github.com/vmware/govmomi@v0.43.0/govc/emacs/govc.el (about)

     1  ;;; govc.el --- Interface to govc for managing VMware ESXi and vCenter
     2  
     3  ;; Author: The govc developers
     4  ;; URL: https://github.com/vmware/govmomi/tree/main/govc/emacs
     5  ;; Keywords: convenience
     6  ;; Version: 0.31.0
     7  ;; Package-Requires: ((emacs "24.3") (dash "1.5.0") (s "1.9.0") (magit-popup "2.0.50") (json-mode "1.6.0"))
     8  
     9  ;; This file is NOT part of GNU Emacs.
    10  
    11  ;; Copyright (c) 2016 VMware, Inc. All Rights Reserved.
    12  ;;
    13  ;; Licensed under the Apache License, Version 2.0 (the "License");
    14  ;; you may not use this file except in compliance with the License.
    15  ;; You may obtain a copy of the License at
    16  ;;
    17  ;; http://www.apache.org/licenses/LICENSE-2.0
    18  ;;
    19  ;; Unless required by applicable law or agreed to in writing, software
    20  ;; distributed under the License is distributed on an "AS IS" BASIS,
    21  ;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    22  ;; See the License for the specific language governing permissions and
    23  ;; limitations under the License.
    24  
    25  ;;; Commentary:
    26  
    27  ;; The goal of this package is to provide a simple interface for commonly used
    28  ;; govc commands within Emacs.  This includes table based inventory/state modes
    29  ;; for vms, hosts, datastores and pools.  The keymap for each mode provides
    30  ;; shortcuts for easily feeding the data in view to other govc commands.
    31  ;;
    32  ;; Within the various govc modes, press `?' to see a popup menu of options.
    33  ;; A menu bar is enabled for certain modes, such as `govc-vm-mode' and `govc-host-mode'.
    34  ;; There is also a `govc' menu at all times under the `Tools' menu.
    35  ;;
    36  ;; The recommended way to install govc.el is via MELPA (http://melpa.org/).
    37  
    38  ;;; Code:
    39  
    40  (eval-when-compile
    41    (require 'cl))
    42  (require 'dash)
    43  (require 'diff)
    44  (require 'dired)
    45  (require 'json-mode)
    46  (require 'magit-popup)
    47  (require 'url-parse)
    48  (require 's)
    49  
    50  (autoload 'auth-source-search "auth-source")
    51  
    52  (defgroup govc nil
    53    "Emacs customization group for govc."
    54    :group 'convenience)
    55  
    56  (defcustom govc-keymap-prefix "C-c ;"
    57    "Prefix for `govc-mode'."
    58    :group 'govc)
    59  
    60  (defcustom govc-command "govc"
    61    "Executable path to the govc utility."
    62    :type 'string
    63    :group 'govc)
    64  
    65  (defvar govc-command-map
    66    (let ((map (make-sparse-keymap)))
    67      (define-key map "h" 'govc-host)
    68      (define-key map "p" 'govc-pool)
    69      (define-key map "v" 'govc-vm)
    70      (define-key map "s" 'govc-datastore)
    71      (define-key map "?" 'govc-popup)
    72      map)
    73    "Keymap for `govc-mode' after `govc-keymap-prefix' was pressed.")
    74  
    75  (defvar govc-mode-map
    76    (let ((map (make-sparse-keymap)))
    77      (define-key map (kbd govc-keymap-prefix) govc-command-map)
    78      map)
    79    "Keymap for `govc-mode'.")
    80  
    81  ;;;###autoload
    82  (define-minor-mode govc-mode
    83    "Running `govc-global-mode' creates key bindings to the various govc modes.
    84  The default prefix is `C-c ;' and can be changed by setting `govc-keymap-prefix'.
    85  
    86  \\{govc-mode-map\}"
    87    nil govc-mode-line govc-mode-map
    88    :group 'govc)
    89  
    90  ;;;###autoload
    91  (define-globalized-minor-mode govc-global-mode govc-mode govc-mode)
    92  
    93  (defcustom govc-mode-line
    94    '(:eval (format " govc[%s]" (or (govc-session-name) "-")))
    95    "Mode line lighter for govc."
    96    :group 'govc
    97    :type 'sexp
    98    :risky t)
    99  
   100  
   101  ;;; Tabulated list mode extensions (derived from https://github.com/Silex/docker.el tabulated-list-ext.el)
   102  (defun govc-tabulated-list-mark ()
   103    "Mark and move to the next line."
   104    (interactive)
   105    (tabulated-list-put-tag (char-to-string dired-marker-char) t))
   106  
   107  (defun govc-tabulated-list-unmark ()
   108    "Unmark and move to the next line."
   109    (interactive)
   110    (tabulated-list-put-tag "" t))
   111  
   112  (defun govc-tabulated-list-toggle-marks ()
   113    "Toggle mark."
   114    (interactive)
   115    (save-excursion
   116      (goto-char (point-min))
   117      (let ((cmd))
   118        (while (not (eobp))
   119          (setq cmd (char-after))
   120          (tabulated-list-put-tag
   121           (if (eq cmd dired-marker-char)
   122               ""
   123             (char-to-string dired-marker-char)) t)))))
   124  
   125  (defun govc-tabulated-list-unmark-all ()
   126    "Unmark all."
   127    (interactive)
   128    (save-excursion
   129      (goto-char (point-min))
   130      (while (not (eobp))
   131        (tabulated-list-put-tag "" t))))
   132  
   133  (defun govc-selection ()
   134    "Get the current selection as a list of names."
   135    (let ((selection))
   136      (save-excursion
   137        (goto-char (point-min))
   138        (while (not (eobp))
   139          (when (eq (char-after) ?*)
   140            (add-to-list 'selection (tabulated-list-get-id)))
   141          (forward-line)))
   142      (or selection (let ((id (tabulated-list-get-id)))
   143                      (if id
   144                          (list id))))))
   145  
   146  (defun govc-do-selection (fn action)
   147    "Call FN with `govc-selection' confirming ACTION."
   148    (let* ((selection (govc-selection))
   149           (count (length selection))
   150           (prompt (if (= count 1)
   151                       (car selection)
   152                     (format "* [%d] marked" count))))
   153      (if (yes-or-no-p (format "%s %s ?" action prompt))
   154          (funcall fn selection))))
   155  
   156  (defun govc-copy-selection ()
   157    "Copy current selection or region to the kill ring."
   158    (interactive)
   159    (if (region-active-p)
   160        (copy-region-as-kill (mark) (point) 'region)
   161      (kill-new (message "%s" (s-join " " (--map (format "\"%s\"" it) (govc-selection)))))))
   162  
   163  (defvar govc-font-lock-keywords
   164    `((,(let ((host-expression "\\b[-a-z0-9]+\\b")) ;; Hostname
   165          (concat
   166           (mapconcat 'identity (make-list 3 host-expression) "\\.")
   167           "\\(\\." host-expression "\\)*")) .
   168           (0 font-lock-variable-name-face))
   169      (,(mapconcat 'identity (make-list 4 "[0-9]+") "\\.") ;; IP address
   170       . (0 font-lock-variable-name-face))
   171      ("\"[^\"]*\"" . (0 font-lock-string-face))
   172      ("'[^']*'" . (0 font-lock-string-face))
   173      ("[.0-9]+%" . (0 font-lock-type-face))
   174      ("\\<\\(success\\|poweredOn\\)\\>" . (1 font-lock-doc-face))
   175      ("\\<\\(error\\|poweredOff\\)\\>" . (1 font-lock-warning-face))
   176      ("\\<\\(running\\|info\\)\\>" . (1 font-lock-variable-name-face))
   177      ("\\<\\(warning\\|suspended\\)\\>" . (1 font-lock-keyword-face))
   178      ("\\<\\(verbose\\|trivia\\)\\>" . (1 whitespace-line))
   179      (,dired-re-maybe-mark . (0 dired-mark-face))
   180      ("types.ManagedObjectReference\\(.*\\)" . (1 dired-directory-face))
   181      ("[^ ]*/$" . (0 dired-directory-face))
   182      ("\\.\\.\\.$" . (0 dired-symlink-face))
   183      ("^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][A-Z][0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9][A-Z]|" . (0 font-lock-comment-face))
   184      ("^\\([ A-Za-z0-9_]+: \\).*" . (1 font-lock-comment-face))
   185      ("<[^>]*>" . (0 font-lock-comment-face))
   186      ("\\[[^]]*\\]" . (0 font-lock-comment-face))
   187      ("([^)]*)" . (0 font-lock-comment-face))))
   188  
   189  (defvar govc-tabulated-list-mode-map
   190    (let ((map (make-sparse-keymap)))
   191      (define-key map "m" 'govc-tabulated-list-mark)
   192      (define-key map "u" 'govc-tabulated-list-unmark)
   193      (define-key map "t" 'govc-tabulated-list-toggle-marks)
   194      (define-key map "U" 'govc-tabulated-list-unmark-all)
   195      (define-key map (kbd "M-&") 'govc-shell-command)
   196      (define-key map (kbd "M-w") 'govc-copy-selection)
   197      (define-key map (kbd "M-E") 'govc-copy-environment)
   198      map)
   199    "Keymap for `govc-tabulated-list-mode'.")
   200  
   201  (define-derived-mode govc-tabulated-list-mode tabulated-list-mode "Tabulated govc"
   202    "Generic table bindings to mark/unmark rows."
   203    (setq-local font-lock-defaults
   204                '(govc-font-lock-keywords t nil nil beginning-of-line)))
   205  
   206  
   207  ;;; Keymap helpers for generating menus and popups
   208  (defun govc-keymap-list (keymap)
   209    "Return a list of (key name function) for govc bindings in the given KEYMAP.
   210  The name returned is the first word of the function `documentation'."
   211    (let ((map))
   212      (map-keymap
   213       (lambda (k f)
   214         (when (keymapp f)
   215           (setq map (append map
   216                             (--map (and (setcar it (kbd (format "M-%s" (char-to-string (car it))))) it)
   217                                    (govc-keymap-list f)))))
   218         (when (and (symbolp f)
   219                    (s-starts-with? "govc-" (symbol-name f)))
   220           (if (not (eq ?? k))
   221               (add-to-list 'map (list k (car (split-string (documentation f))) f))))) keymap)
   222      map))
   223  
   224  (defun govc-keymap-menu (keymap)
   225    "Return a list of [key function t] for govc bindings in the given KEYMAP.
   226  For use with `easy-menu-define'."
   227    (-map (lambda (item)
   228            (vector (nth 1 item) (nth 2 item) t))
   229          (govc-keymap-list keymap)))
   230  
   231  (defun govc-key-description (key)
   232    "Call `key-description' ensuring KEY is a sequence."
   233    (key-description (if (integerp key) (list key) key)))
   234  
   235  (defun govc-keymap-list-to-help (keymap)
   236    "Convert KEYMAP to list of help text."
   237    (--map (list (govc-key-description (car it))
   238                 (car (split-string (documentation (nth 2 it)) "\\.")))
   239           keymap))
   240  
   241  (defun govc-keymap-popup-help ()
   242    "Default keymap help for `govc-keymap-popup'."
   243    (append (govc-keymap-list-to-help (govc-keymap-list govc-tabulated-list-mode-map))
   244            '(("g" "Refresh current buffer")
   245              ("C-h m" "Show all key bindings"))))
   246  
   247  (defun govc-keymap-popup (keymap)
   248    "Convert a `govc-keymap-list' using KEYMAP for use with `magit-define-popup'.
   249  Keys in the ASCII range of 32-97 are mapped to popup commands, all others are listed as help text."
   250    (let* ((maps (--separate (and (integerp (car it))
   251                                  (>= (car it) 32)
   252                                  (<= (car it) 97))
   253                             (govc-keymap-list keymap)))
   254           (help (govc-keymap-list-to-help (cadr maps))))
   255      (append
   256       '("Commands")
   257       (car maps)
   258       (list (s-join "\n" (--map (format " %-6s %s" (car it) (cadr it))
   259                                 (append help (govc-keymap-popup-help))))
   260             nil))))
   261  
   262  
   263  ;;; govc process helpers
   264  (defcustom govc-urls nil
   265    "List of URLs for use with `govc-session'.
   266  The `govc-session-name' displayed by `govc-mode-line' uses `url-target' (anchor)
   267  if set, otherwise `url-host' is used.
   268  
   269  Example:
   270  ```
   271    (setq govc-urls '(\"root:vagrant@localhost:18443#Vagrant-ESXi\"
   272                      \"root:password@192.168.1.192#Intel-NUC\"
   273                      \"Administrator@vsphere.local:password!@vcva-clovervm\"))
   274  ```
   275  To enter a URL that is not in the list, prefix `universal-argument', for example:
   276  
   277    `\\[universal-argument] \\[govc-vm]'
   278  
   279  To avoid putting your credentials in a variable, you can use the
   280  auth-source search integration.
   281  
   282  ```
   283    (setq govc-urls '(\"myserver-vmware-2\"))
   284  ```
   285  
   286  And then put this line in your `auth-sources' (e.g. `~/.authinfo.gpg'):
   287  ```
   288      machine myserver-vmware-2 login tzz password mypass url \"myserver-vmware-2.some.domain.here:443?insecure=true\"
   289  ```
   290  
   291  Which will result in the URL \"tzz:mypass@myserver-vmware-2.some.domain.here:443?insecure=true\".
   292  For more details on `auth-sources', see Info node `(auth) Help for users'.
   293  
   294  When in `govc-vm' or `govc-host' mode, a default URL is composed with the
   295  current session credentials and the IP address of the current vm/host and
   296  the vm/host name as the session name.  This makes it easier to connect to
   297  nested ESX/vCenter VMs or directly to an ESX host."
   298    :group 'govc
   299    :type '(repeat (string :tag "vcenter URL or auth-source machine reference")))
   300  
   301  (defvar-local govc-session-url nil
   302    "ESX or vCenter URL set by `govc-session' via `govc-urls' selection.")
   303  
   304  (defvar-local govc-session-path nil)
   305  
   306  (defvar-local govc-session-insecure nil
   307    "Skip verification of server certificate when true.
   308  This variable is set to the value of the `GOVC_INSECURE' env var by default.
   309  It can also be set per-url via the query string (insecure=true).  For example:
   310  ```
   311    (setq govc-urls '(\"root:password@hostname?insecure=true\"))
   312  ```")
   313  
   314  (defvar-local govc-session-datacenter nil
   315    "Datacenter to use for the current `govc-session'.
   316  If the endpoint has a single Datacenter it will be used by default, otherwise
   317  `govc-session' will prompt for selection.  It can also be set per-url via the
   318  query string.  For example:
   319  ```
   320    (setq govc-urls '(\"root:password@hostname?datacenter=dc1\"))
   321  ```")
   322  
   323  (defvar-local govc-session-datastore nil
   324    "Datastore to use for the current `govc-session'.
   325  If the endpoint has a single Datastore it will be used by default, otherwise
   326  `govc-session' will prompt for selection.  It can also be set per-url via the
   327  query string.  For example:
   328  ```
   329    (setq govc-urls '(\"root:password@hostname?datastore=vsanDatastore\"))
   330  ```")
   331  
   332  (defvar-local govc-session-network nil
   333    "Network to use for the current `govc-session'.")
   334  
   335  (defvar-local govc-filter nil
   336    "Resource path filter.")
   337  
   338  (defvar-local govc-args nil
   339    "Additional govc arguments.")
   340  
   341  (defun govc-session-name ()
   342    "Return a name for the current session.
   343  Derived from `govc-session-url' if set, otherwise from the 'GOVC_URL' env var.
   344  Return value is the url anchor if set, otherwise the hostname is returned."
   345    (let* ((u (or govc-session-url (getenv "GOVC_URL")))
   346           (url (if u (govc-url-parse u))))
   347      (if url
   348          (concat (or (url-target url) (url-host url)) govc-session-path))))
   349  
   350  (defun govc-format-command (command &rest args)
   351    "Format govc COMMAND ARGS."
   352    (format "%s %s %s" govc-command command
   353            (s-join " " (--map (format "\"%s\"" it)
   354                               (-flatten (-non-nil args))))))
   355  
   356  (defconst govc-environment-map (--map (cons (concat "GOVC_" (upcase it))
   357                                              (intern (concat "govc-session-" it)))
   358                                        '("url" "insecure" "datacenter" "datastore" "network"))
   359  
   360    "Map of `GOVC_*' environment variable names to `govc-session-*' symbol names.")
   361  
   362  (defun govc-environment (&optional unset)
   363    "Return `process-environment' for govc.
   364  Optionally clear govc env if UNSET is non-nil."
   365    (let ((process-environment (copy-sequence process-environment)))
   366      (dolist (e govc-environment-map)
   367        (setenv (car e) (unless unset (symbol-value (cdr e)))))
   368      process-environment))
   369  
   370  (defun govc-export-environment (arg)
   371    "Set if ARG is \\[universal-argument], unset if ARG is \\[negative-argument]."
   372    (if (equal arg '-)
   373        (progn (setq process-environment (govc-environment t))
   374               (cons "unset" (--map (car it)
   375                                    govc-environment-map)))
   376      (progn (setq process-environment (govc-environment))
   377             (cons "export" (--map (format "%s='%s'" (car it) (or (symbol-value (cdr it)) ""))
   378                                   govc-environment-map)))))
   379  
   380  (defun govc-copy-environment (&optional arg)
   381    "Export session to `process-environment' and `kill-ring'.
   382  Optionally set `GOVC_*' vars in `process-environment' using prefix
   383  \\[universal-argument] ARG or unset with prefix \\[negative-argument] ARG."
   384    (interactive "P")
   385    (message (kill-new (if arg (s-join " " (govc-export-environment arg)) govc-session-url))))
   386  
   387  (defun govc-process (command handler)
   388    "Run COMMAND, calling HANDLER upon successful exit of the process."
   389    (message "%s" command)
   390    (let ((process-environment (govc-environment))
   391          (exit-code))
   392      (add-to-list 'govc-command-history command)
   393      (with-temp-buffer
   394        (setq exit-code (call-process-shell-command command nil (current-buffer)))
   395        (if (zerop exit-code)
   396            (funcall handler)
   397          (error (buffer-string))))))
   398  
   399  (defun govc (command &rest args)
   400    "Execute govc COMMAND with ARGS.
   401  Return value is `buffer-string' split on newlines."
   402    (govc-process (govc-format-command command args)
   403                  (lambda ()
   404                    (split-string (buffer-string) "\n" t))))
   405  
   406  (defun govc-json (command &rest args)
   407    "Execute govc COMMAND passing arguments ARGS.
   408  Return value is `json-read'."
   409    (govc-process (govc-format-command command (cons "-json" args))
   410                  (lambda ()
   411                    (goto-char (point-min))
   412                    (let ((json-object-type 'plist))
   413                      (json-read)))))
   414  
   415  (defun govc-ls-datacenter ()
   416    "List datacenters."
   417    (govc "ls" "-t" "Datacenter" "./..."))
   418  
   419  (defun govc-object-prompt (prompt ls)
   420    "PROMPT for object name via LS function.  Return object without PROMPT if there is just one instance."
   421    (let ((objs (if (listp ls) ls (funcall ls))))
   422      (if (eq 1 (length objs))
   423          (car objs)
   424        (completing-read prompt objs))))
   425  
   426  (defun govc-url-parse (url)
   427    "A `url-generic-parse-url' wrapper to handle URL with password, but no scheme.
   428  Also fixes the case where user contains an '@'."
   429    (let* ((full (s-contains? "://" url))
   430           (u (url-generic-parse-url (concat (unless full "https://") url))))
   431      (unless full
   432        (setf (url-type u) nil)
   433        (setf (url-fullness u) nil))
   434      (if (s-contains? "@" (url-host u))
   435          (let* ((h (split-string (url-host u) "@"))
   436                 (p (split-string (car h) ":")))
   437            (setf (url-host u) (cadr h))
   438            (setf (url-user u) (concat (url-user u) "@" (car p)))
   439            (setf (url-password u) (cadr p))))
   440      u))
   441  
   442  (defun govc-url-default ()
   443    "Default URL when creating a new session."
   444    (if govc-session-url
   445        (let ((url (govc-url-parse govc-session-url)))
   446          (if (equal major-mode 'govc-host-mode)
   447              (progn (setf (url-host url) (govc-table-column-value "Name"))
   448                     (setf (url-target url) nil))
   449            (progn (setf (url-host url) (govc-table-column-value "IP address"))
   450                   (setf (url-target url) (govc-table-column-value "Name"))
   451                   ;; default url-user to Administrator@$domain when connecting to a vCenter VM
   452                   (let ((sts (ignore-errors (govc "sso.service.ls" "-t" "cs.identity" "-P" "wsTrust" "-U" "-u" (url-host url)))))
   453                     (if sts (setf (url-user url) (concat "Administrator@" (file-name-nondirectory (car sts))))))))
   454          (setf (url-filename url) "") ; erase query string
   455          (if (string-empty-p (url-user url))
   456              (setf (url-user url) "root")) ; local workstation url has no user set
   457          (url-recreate-url url))))
   458  
   459  (defun govc-urls-completing-read ()
   460    "A wrapper for `completing-read' to mask credentials in `govc-urls'."
   461    (let ((alist))
   462      (dolist (ent govc-urls)
   463        (let ((u (govc-url-parse ent)))
   464          (setf (url-password u) nil)
   465          (add-to-list 'alist `(,(url-recreate-url u) . ,ent) t)))
   466      (let ((u (completing-read "govc url: " (-map 'car alist))))
   467        (cdr (assoc u alist)))))
   468  
   469  (defun govc-session-url-lookup-auth-source (url-or-address)
   470    "Check if URL-OR-ADDRESS is a logical name in the authinfo file.
   471  Given URL-OR-ADDRESS `myserver-vmware-2' this function will find
   472  a line like
   473      machine myserver-vmware-2 login tzz password mypass url \"myserver-vmware-2.some.domain.here:443?insecure=true\"
   474  
   475  and will return the URL \"tzz:mypass@myserver-vmware-2.some.domain.here:443?insecure=true\".
   476  
   477  If the line is not found, the original URL-OR-ADDRESS is
   478  returned, assuming that's what the user wanted."
   479    (let ((found (nth 0 (auth-source-search :max 1
   480                                            :host url-or-address
   481                                            :require '(:user :secret :url)
   482                                            :create nil))))
   483      (if found
   484          (format "%s:%s@%s"
   485                  (plist-get found :user)
   486                  (let ((secret (plist-get found :secret)))
   487                    (if (functionp secret)
   488                        (funcall secret)
   489                      secret))
   490                  (plist-get found :url))
   491        url-or-address)))
   492  
   493  (defun govc-session-set-url (url)
   494    "Set `govc-session-url' to URL and optionally set other govc-session-* variables via URL query."
   495    ;; Replace the original URL with the auth-source lookup if there is no user.
   496    (unless (url-user (govc-url-parse url))
   497      (setq url (govc-session-url-lookup-auth-source url)))
   498  
   499    (let ((q (cdr (url-path-and-query (govc-url-parse url)))))
   500      (dolist (opt (if q (url-parse-query-string q)))
   501        (let ((var (intern (concat "govc-session-" (car opt)))))
   502          (if (boundp var)
   503              (set var (cadr opt))))))
   504    (setq govc-session-url url))
   505  
   506  (defun govc-session ()
   507    "Initialize a govc session."
   508    (interactive)
   509    (let ((url (if (or current-prefix-arg (eq 0 (length govc-urls)))
   510                   (read-string "govc url: " (govc-url-default))
   511                 (if (eq 1 (length govc-urls))
   512                     (car govc-urls)
   513                   (govc-urls-completing-read)))))
   514      ;; Wait until this point to clear so current session is preserved in the
   515      ;; event of `keyboard-quit' in `read-string'.
   516      (setq govc-session-datacenter nil
   517            govc-session-datastore nil
   518            govc-session-network nil
   519            govc-filter nil)
   520      (govc-session-set-url url))
   521    (unless govc-session-insecure
   522      (setq govc-session-insecure (or (getenv "GOVC_INSECURE")
   523                                      (completing-read "govc insecure: " '("true" "false")))))
   524    (unless govc-session-datacenter
   525      (setq govc-session-datacenter (govc-object-prompt "govc datacenter: " 'govc-ls-datacenter)))
   526    (add-to-list 'govc-urls govc-session-url))
   527  
   528  (defalias 'govc-current-session 'buffer-local-variables)
   529  
   530  (defun govc-session-clone (session)
   531    "Clone a session from SESSION buffer locals."
   532    (dolist (v session)
   533      (let ((s (car v)))
   534        (when (s-starts-with? "govc-session-" (symbol-name s))
   535          (set s (assoc-default s session))))))
   536  
   537  (defvar govc-command-history nil
   538    "History list for govc commands used by `govc-shell-command'.")
   539  
   540  (defvar govc-shell--revert-cmd nil)
   541  
   542  (defun govc-shell--revert-function (&optional _ _)
   543    "Re-run the buffer's most recent govc-shell-run command."
   544    (apply (car govc-shell--revert-cmd) (cdr govc-shell--revert-cmd)))
   545  
   546  (defun govc-shell-filter (proc string)
   547    "Process filter for govc-shell PROC, append STRING."
   548    (when (buffer-live-p (process-buffer proc))
   549      (with-current-buffer (process-buffer proc)
   550        (let ((moving (= (point) (process-mark proc))))
   551          (save-excursion
   552            (let ((inhibit-read-only t))
   553              (goto-char (process-mark proc))
   554              (insert string)
   555              (set-marker (process-mark proc) (point))))
   556          (display-buffer (process-buffer proc))
   557          (if moving
   558              (with-selected-window (get-buffer-window (current-buffer))
   559                (goto-char (point-max))))))))
   560  
   561  (defun govc-shell-run (name args buffer)
   562    "Run NAME command with ARGS in BUFFER."
   563    (with-current-buffer (if (stringp buffer) (get-buffer-create buffer) buffer)
   564      (let ((proc (get-buffer-process (current-buffer)))
   565            (process-environment (govc-environment))
   566            (session (govc-current-session))
   567            (inhibit-read-only t))
   568        (when proc
   569          (set-process-filter proc nil)
   570          (delete-process proc))
   571        (erase-buffer)
   572        (if (--any? (member (file-name-extension it) '("vmx" "vmdk")) args)
   573            (conf-mode)
   574          (govc-shell-mode))
   575        (govc-session-clone session)
   576        (setq-local govc-shell--revert-cmd `(govc-shell-run ,name ,args ,(current-buffer)))
   577        (setq mode-line-process '(:propertize ":run" face compilation-mode-line-run))
   578        (setq proc (apply 'start-process name (current-buffer) name args))
   579        (set-process-sentinel proc 'govc-shell-process-sentinel)
   580        (set-process-filter proc 'govc-shell-filter))))
   581  
   582  (defun govc-shell-kill ()
   583    "Kill the process started by \\[govc-shell-command]."
   584    (interactive)
   585    (let ((buffer (current-buffer)))
   586      (if (get-buffer-process buffer)
   587          (interrupt-process (get-buffer-process buffer))
   588        (error "The %s process is not running" (downcase mode-name)))))
   589  
   590  (defun govc-shell-process-sentinel (process event)
   591    "Process sentinel used by `govc-shell-run'.  When PROCESS exits EVENT is logged."
   592    (when (memq (process-status process) '(exit signal))
   593      (with-current-buffer (process-buffer process)
   594        (setq mode-line-process nil)
   595        (message "%s %s" (process-name process) (substring event 0 -1)))))
   596  
   597  (defvar govc-shell-mode-map
   598    (let ((map (make-sparse-keymap)))
   599      (define-key map (kbd "C-c C-k") 'govc-shell-kill)
   600      map))
   601  
   602  (define-derived-mode govc-shell-mode special-mode "govc-shell"
   603    "Mode for running govc commands."
   604    (setq-local font-lock-defaults '(govc-font-lock-keywords))
   605    (setq-local revert-buffer-function #'govc-shell--revert-function))
   606  
   607  (defun govc-shell-command (&optional cmd buffer)
   608    "Shell CMD in BUFFER with current `govc-session' exported as GOVC_ env vars."
   609    (interactive)
   610    (let* ((session (govc-current-session))
   611           (args (if cmd (--map (format "%s" it) (-flatten (-non-nil (list govc-command cmd))))
   612                   (split-string-and-unquote (read-shell-command "command: " nil 'govc-command-history)))))
   613      (with-current-buffer (get-buffer-create (or buffer "*govc*"))
   614        (govc-session-clone session)
   615        (govc-shell-run (car args) (cdr args) (current-buffer)))))
   616  
   617  (defcustom govc-max-events 100
   618    "Limit events output to the last N events."
   619    :type 'integer
   620    :group 'govc)
   621  
   622  (defun govc-events ()
   623    "Events via govc events -n `govc-max-events'."
   624    (interactive)
   625    (govc-shell-command
   626     (list "events" "-l" "-n" govc-max-events (if current-prefix-arg "-f") (govc-selection)) "*govc-event*"))
   627  
   628  (defun govc-tasks ()
   629    "Tasks via govc tasks."
   630    (interactive)
   631    (govc-shell-command
   632     (list "tasks" "-l" "-n" govc-max-events (if current-prefix-arg "-f") (govc-selection)) "*govc-task*"))
   633  
   634  (defun govc-logs ()
   635    "Logs via govc logs -n `govc-max-events'."
   636    (interactive)
   637    (govc-shell-command
   638     (let ((host (govc-selection)))
   639       (list "logs" "-n" govc-max-events (if current-prefix-arg "-f") (if host (list "-host" host)))) "*govc-log*"))
   640  
   641  (defun govc-parse-info (output)
   642    "Parse govc info command OUTPUT."
   643    (let* ((entries)
   644           (entry)
   645           (entry-key))
   646      (-each output
   647        (lambda (line)
   648          (let* ((ix (s-index-of ":" line))
   649                 (key (s-trim (substring line 0 ix)))
   650                 (val (s-trim (substring line (+ ix 1)))))
   651            (unless entry-key
   652              (setq entry-key key))
   653            (when (s-equals? key entry-key)
   654              (setq entry (make-hash-table :test 'equal))
   655              (add-to-list 'entries entry))
   656            (puthash key val entry))))
   657      entries))
   658  
   659  (defun govc-table-column-names ()
   660    "Return a list of column names from `tabulated-list-format'."
   661    (--map (car (aref tabulated-list-format it))
   662           (number-sequence 0 (- (length tabulated-list-format) 1))))
   663  
   664  (defun govc-table-column-value (key)
   665    "Return current column value for given KEY."
   666    (let ((names (govc-table-column-names))
   667          (entry (tabulated-list-get-entry))
   668          (value))
   669      (dotimes (ix (- (length names) 1))
   670        (if (s-equals? key (nth ix names))
   671            (setq value (elt entry ix))))
   672      value))
   673  
   674  (defun govc-table-info (command &optional args)
   675    "Convert `govc-parse-info' COMMAND ARGS output to `tabulated-list-entries' format."
   676    (let ((names (govc-table-column-names)))
   677      (-map (lambda (info)
   678              (let ((id (or (gethash "Path" info)
   679                            (gethash (car names) info))))
   680                (list id (vconcat
   681                          (--map (or (gethash it info) "-")
   682                                 names)))))
   683            (govc-parse-info (govc command args)))))
   684  
   685  (defun govc-map-info (command &optional args)
   686    "Populate key=val map table with govc COMMAND ARGS output."
   687    (-map (lambda (line)
   688            (let* ((ix (s-index-of ":" line))
   689                   (key (s-trim (substring line 0 ix)))
   690                   (val (s-trim (substring line (+ ix 1)))))
   691              (list key (vector key val))))
   692          (govc command args)))
   693  
   694  (defun govc-map-info-table (entries)
   695    "Tabulated `govc-map-info' data via ENTRIES."
   696    (let ((session (govc-current-session))
   697          (args (append govc-args (govc-selection)))
   698          (buffer (get-buffer-create "*govc-info*")))
   699      (pop-to-buffer buffer)
   700      (tabulated-list-mode)
   701      (setq govc-args args)
   702      (govc-session-clone session)
   703      (setq tabulated-list-format [("Name" 50)
   704                                   ("Value" 50)]
   705            tabulated-list-padding 2
   706            tabulated-list-entries entries)
   707      (tabulated-list-print)))
   708  
   709  (defun govc-type-list-entries (command)
   710    "Convert govc COMMAND type table output to `tabulated-list-entries'."
   711    (-map (lambda (line)
   712            (let* ((entry (s-split-up-to " " (s-collapse-whitespace line) 2))
   713                   (name (car entry))
   714                   (type (nth 1 entry))
   715                   (value (car (last entry))))
   716              (list name (vector name type value))))
   717          (govc command govc-args)))
   718  
   719  (defun govc-json-info-selection (command)
   720    "Run govc COMMAND -json on `govc-selection'."
   721    (if current-prefix-arg
   722        (--each (govc-selection) (govc-json-info command it))
   723      (govc-json-info command (govc-selection))))
   724  
   725  (defun govc-json-diff ()
   726    "Diff two *govc-json* buffers in view."
   727    (let ((buffers))
   728      (-each (window-list-1)
   729        (lambda (w)
   730          (with-current-buffer (window-buffer w)
   731            (if (and (eq major-mode 'json-mode)
   732                     (s-starts-with? "*govc-json*" (buffer-name)))
   733                (push (current-buffer) buffers)))) )
   734      (if (= (length buffers) 2)
   735          (pop-to-buffer
   736           (diff-no-select (car buffers) (cadr buffers))))))
   737  
   738  (defun govc-json-info (command selection)
   739    "Run govc COMMAND -json on SELECTION."
   740    (govc-process (govc-format-command command "-json" govc-args selection)
   741                  (lambda ()
   742                    (let ((buffer (get-buffer-create (concat "*govc-json*" (if current-prefix-arg selection)))))
   743                      (copy-to-buffer buffer (point-min) (point-max))
   744                      (with-current-buffer buffer
   745                        (json-mode)
   746                        (json-pretty-print-buffer))
   747                      (display-buffer buffer))))
   748    (if current-prefix-arg
   749        (govc-json-diff)))
   750  
   751  (defun govc-mode-new-session ()
   752    "Connect new session for the current govc mode."
   753    (interactive)
   754    (call-interactively 'govc-session)
   755    (revert-buffer))
   756  
   757  (defun govc-host-with-session ()
   758    "Host-mode with current session."
   759    (interactive)
   760    (govc-host nil (govc-current-session)))
   761  
   762  (defun govc-vm-with-session ()
   763    "VM-mode with current session."
   764    (interactive)
   765    (govc-vm nil (govc-current-session)))
   766  
   767  (defun govc-datastore-with-session ()
   768    "Datastore-mode with current session."
   769    (interactive)
   770    (govc-datastore nil (govc-current-session)))
   771  
   772  (defun govc-pool-with-session ()
   773    "Pool-mode with current session."
   774    (interactive)
   775    (govc-pool nil (govc-current-session)))
   776  
   777  
   778  ;;; govc object mode
   779  (defvar-local govc-object-history '("-")
   780    "History list of visited objects.")
   781  
   782  (defun govc-object-collect ()
   783    "Wrapper for govc object.collect."
   784    (interactive)
   785    (let ((id (car govc-args)))
   786      (add-to-list 'govc-object-history id)
   787      (setq govc-session-path id))
   788    (govc-type-list-entries "object.collect"))
   789  
   790  (defun govc-object-collect-selection (&optional json)
   791    "Expand object selection via govc object.collect.
   792  Optionally specify JSON encoding."
   793    (interactive)
   794    (let* ((entry (or (tabulated-list-get-entry) (error "No entry")))
   795           (name (elt entry 0))
   796           (type (elt entry 1))
   797           (val (elt entry 2)))
   798  
   799      (setq govc-args (list (car govc-args) name))
   800  
   801      (cond
   802       ((s-blank? val))
   803       ((and (not json) (s-ends-with? "types.ManagedObjectReference" type))
   804        (let ((ids (govc "ls" "-L" (split-string val ","))))
   805          (setq govc-args (list (govc-object-prompt "moid: " ids)))))
   806       ((string= val "...")
   807        (if (s-starts-with? "[]" type) (setq json t))))
   808  
   809      (if json
   810          (govc-json-info "object.collect" nil)
   811        (tabulated-list-revert))))
   812  
   813  (defun govc-object-collect-selection-json ()
   814    "JSON object selection via govc object.collect."
   815    (interactive)
   816    (govc-object-collect-selection t))
   817  
   818  (defun govc-object-next ()
   819    "Next managed object reference."
   820    (interactive)
   821    (if (search-forward "types.ManagedObjectReference" nil t)
   822        (progn (govc-tabulated-list-unmark-all)
   823               (tabulated-list-put-tag (char-to-string dired-marker-char)))
   824      (goto-char (point-min))))
   825  
   826  (defun govc-object-collect-parent ()
   827    "Parent object selection if reachable, otherwise prompt with `govc-object-history'."
   828    (interactive)
   829    (if (cadr govc-args)
   830        (let ((prop (butlast (split-string (cadr govc-args) "\\."))))
   831          (setq govc-args (list (car govc-args) (if prop (s-join "." prop)))))
   832      (save-excursion
   833        (goto-char (point-min))
   834        (if (re-search-forward "^[[:space:]]*parent" nil t)
   835            (govc-object-collect-selection)
   836          (let ((id (govc-object-prompt "moid: " govc-object-history)))
   837            (setq govc-args (list id (if (string= id "-") "content")))))))
   838    (tabulated-list-revert))
   839  
   840  (defun govc-object (&optional moid property session)
   841    "Object browser aka MOB (Managed Object Browser).
   842  Optionally starting at MOID and PROPERTY if given.
   843  Inherit SESSION if given."
   844    (interactive)
   845    (let ((buffer (get-buffer-create "*govc-object*")))
   846      (if (called-interactively-p 'interactive)
   847          (switch-to-buffer buffer)
   848        (pop-to-buffer buffer))
   849      (govc-object-mode)
   850      (if session
   851          (govc-session-clone session)
   852        (call-interactively 'govc-session))
   853      (setq govc-args (list (or moid "-") property))
   854      (tabulated-list-print)))
   855  
   856  (defun govc-object-info ()
   857    "Object browser via govc object.collect on `govc-selection'."
   858    (interactive)
   859    (if (equal major-mode 'govc-object-mode)
   860        (progn
   861          (setq govc-args (list (govc-object-prompt "moid: " govc-object-history)))
   862          (tabulated-list-revert))
   863      (govc-object (tabulated-list-get-id) nil (govc-current-session))))
   864  
   865  (defvar govc-object-mode-map
   866    (let ((map (make-sparse-keymap)))
   867      (define-key map "J" 'govc-object-collect-selection-json)
   868      (define-key map "N" 'govc-object-next)
   869      (define-key map "O" 'govc-object-info)
   870      (define-key map (kbd "DEL") 'govc-object-collect-parent)
   871      (define-key map (kbd "RET") 'govc-object-collect-selection)
   872      (define-key map "?" 'govc-object-popup)
   873      map)
   874    "Keymap for `govc-object-mode'.")
   875  
   876  (define-derived-mode govc-object-mode govc-tabulated-list-mode "Object"
   877    "Major mode for handling a govc object."
   878    (setq tabulated-list-format [("Name" 40 t)
   879                                 ("Type" 40 t)
   880                                 ("Value" 40 t)]
   881          tabulated-list-padding 2
   882          tabulated-list-entries #'govc-object-collect)
   883    (tabulated-list-init-header))
   884  
   885  (magit-define-popup govc-object-popup
   886    "Object popup."
   887    :actions (govc-keymap-popup govc-object-mode-map))
   888  
   889  
   890  ;;; govc metric mode
   891  (defun govc-metric-sample ()
   892    "Sample metrics."
   893    (interactive)
   894    (govc-shell-command (list "metric.sample" govc-args govc-filter (govc-selection))))
   895  
   896  (defun govc-metric-sample-plot ()
   897    "Plot metric sample."
   898    (interactive)
   899    (let* ((type (if (and (display-images-p) (not (eq current-prefix-arg '-))) 'png 'dumb))
   900           (max (if (member "-i" govc-args) "60" "180"))
   901           (args (append govc-args (list "-n" max "-plot" type govc-filter)))
   902           (session (govc-current-session))
   903           (metrics (govc-selection))
   904           (inhibit-read-only t))
   905      (with-current-buffer (get-buffer-create "*govc*")
   906        (govc-session-clone session)
   907        (erase-buffer)
   908        (delete-other-windows)
   909        (if (eq type 'dumb)
   910            (split-window-right)
   911          (split-window-below))
   912        (display-buffer-use-some-window (current-buffer) '((inhibit-same-window . t)))
   913        (--each metrics
   914          (let* ((cmd (govc-format-command "metric.sample" args it))
   915                 (data (govc-process cmd 'buffer-string)))
   916            (if (eq type 'dumb)
   917                (insert data)
   918              (insert-image (create-image (string-as-unibyte data) type t))))))))
   919  
   920  (defun govc-metric-select (metrics)
   921    "Select metric names.  METRICS is a regexp."
   922    (interactive (list (read-regexp "Select metrics" (regexp-quote ".usage."))))
   923    (save-excursion
   924      (goto-char (point-min))
   925      (while (not (eobp))
   926        (if (string-match-p metrics (tabulated-list-get-id))
   927            (govc-tabulated-list-mark)
   928          (govc-tabulated-list-unmark)))))
   929  
   930  (defun govc-metric-info ()
   931    "Wrapper for govc metric.info."
   932    (govc-table-info "metric.info" (list govc-args (car govc-filter))))
   933  
   934  (defvar govc-metric-mode-map
   935    (let ((map (make-sparse-keymap)))
   936      (define-key map (kbd "RET") 'govc-metric-sample)
   937      (define-key map (kbd "P") 'govc-metric-sample-plot)
   938      (define-key map (kbd "s") 'govc-metric-select)
   939      map)
   940    "Keymap for `govc-metric-mode'.")
   941  
   942  (defun govc-metric ()
   943    "Metrics info."
   944    (interactive)
   945    (let ((session (govc-current-session))
   946          (filter (or (govc-selection) (list govc-session-path)))
   947          (buffer (get-buffer-create "*govc-metric*")))
   948      (pop-to-buffer buffer)
   949      (govc-metric-mode)
   950      (govc-session-clone session)
   951      (if current-prefix-arg (setq govc-args '("-i" "300")))
   952      (setq govc-filter filter)
   953      (tabulated-list-print)))
   954  
   955  (define-derived-mode govc-metric-mode govc-tabulated-list-mode "Metric"
   956    "Major mode for handling a govc metric."
   957    (setq tabulated-list-format [("Name" 35 t)
   958                                 ("Group" 15 t)
   959                                 ("Unit" 4 t)
   960                                 ("Level" 5 t)
   961                                 ("Summary" 50)]
   962          tabulated-list-sort-key (cons "Name" nil)
   963          tabulated-list-padding 2
   964          tabulated-list-entries #'govc-metric-info)
   965    (tabulated-list-init-header))
   966  
   967  
   968  ;;; govc host mode
   969  (defun govc-ls-host ()
   970    "List hosts."
   971    (govc "ls" "-t" "HostSystem" "./..."))
   972  
   973  (defun govc-esxcli-netstat-info ()
   974    "Wrapper for govc host.esxcli network ip connection list."
   975    (govc-table-info "host.esxcli"
   976                     (append govc-args '("-hints=false" "--" "network" "ip" "connection" "list"))))
   977  
   978  (defun govc-esxcli-netstat (host)
   979    "Tabulated `govc-esxcli-netstat-info' HOST."
   980    (interactive (list (govc-object-prompt "Host: " 'govc-ls-host)))
   981    (let ((session (govc-current-session))
   982          (buffer (get-buffer-create "*govc-esxcli*")))
   983      (pop-to-buffer buffer)
   984      (tabulated-list-mode)
   985      (setq govc-args (list "-host" host))
   986      (govc-session-clone session)
   987      (setq tabulated-list-format [("CCAlgo" 10 t)
   988                                   ("ForeignAddress" 20 t)
   989                                   ("LocalAddress" 20 t)
   990                                   ("Proto" 5 t)
   991                                   ("RecvQ" 5 t)
   992                                   ("SendQ" 5 t)
   993                                   ("State" 15 t)
   994                                   ("WorldID" 7 t)
   995                                   ("WorldName" 10 t)]
   996            tabulated-list-padding 2
   997            tabulated-list-entries #'govc-esxcli-netstat-info)
   998      (tabulated-list-init-header)
   999      (tabulated-list-print)))
  1000  
  1001  (defun govc-host-esxcli-netstat ()
  1002    "Netstat via `govc-esxcli-netstat-info' with current host id."
  1003    (interactive)
  1004    (govc-esxcli-netstat (tabulated-list-get-id)))
  1005  
  1006  (defun govc-host-info ()
  1007    "Wrapper for govc host.info."
  1008    (govc-table-info "host.info" (or govc-filter "*")))
  1009  
  1010  (defun govc-host-json-info ()
  1011    "JSON via govc host.info -json on current selection."
  1012    (interactive)
  1013    (govc-json-info-selection "host.info"))
  1014  
  1015  (defvar govc-host-mode-map
  1016    (let ((map (make-sparse-keymap)))
  1017      (define-key map "E" 'govc-events)
  1018      (define-key map "L" 'govc-logs)
  1019      (define-key map "J" 'govc-host-json-info)
  1020      (define-key map "M" 'govc-metric)
  1021      (define-key map "N" 'govc-host-esxcli-netstat)
  1022      (define-key map "O" 'govc-object-info)
  1023      (define-key map "T" 'govc-tasks)
  1024      (define-key map "c" 'govc-mode-new-session)
  1025      (define-key map "p" 'govc-pool-with-session)
  1026      (define-key map "s" 'govc-datastore-with-session)
  1027      (define-key map "v" 'govc-vm-with-session)
  1028      (define-key map "?" 'govc-host-popup)
  1029      map)
  1030    "Keymap for `govc-host-mode'.")
  1031  
  1032  (defun govc-host (&optional filter session)
  1033    "Host info via govc.
  1034  Optionally filter by FILTER and inherit SESSION."
  1035    (interactive)
  1036    (let ((buffer (get-buffer-create "*govc-host*")))
  1037      (pop-to-buffer buffer)
  1038      (govc-host-mode)
  1039      (if session
  1040          (govc-session-clone session)
  1041        (call-interactively 'govc-session))
  1042      (setq govc-filter filter)
  1043      (tabulated-list-print)))
  1044  
  1045  (define-derived-mode govc-host-mode govc-tabulated-list-mode "Host"
  1046    "Major mode for handling a list of govc hosts."
  1047    (setq tabulated-list-format [("Name" 30 t)
  1048                                 ("Logical CPUs" 20 t)
  1049                                 ("CPU usage" 25 t)
  1050                                 ("Memory" 10 t)
  1051                                 ("Memory usage" 25 t)
  1052                                 ("Manufacturer" 13 t)
  1053                                 ("Boot time" 15 t)]
  1054          tabulated-list-sort-key (cons "Name" nil)
  1055          tabulated-list-padding 2
  1056          tabulated-list-entries #'govc-host-info)
  1057    (tabulated-list-init-header))
  1058  
  1059  (magit-define-popup govc-host-popup
  1060    "Host popup."
  1061    :actions (govc-keymap-popup govc-host-mode-map))
  1062  
  1063  (easy-menu-define govc-host-mode-menu govc-host-mode-map
  1064    "Host menu."
  1065    (cons "Host" (govc-keymap-menu govc-host-mode-map)))
  1066  
  1067  
  1068  ;;; govc pool mode
  1069  (defun govc-pool-destroy (name)
  1070    "Destroy pool with given NAME."
  1071    (interactive (list (completing-read "Destroy pool: " (govc "ls" "-t" "ResourcePool" "host/*"))))
  1072    (govc "pool.destroy" name))
  1073  
  1074  (defun govc-pool-destroy-selection ()
  1075    "Destroy via `govc-pool-destroy' on the pool selection."
  1076    (interactive)
  1077    (govc-do-selection 'govc-pool-destroy "Delete")
  1078    (tabulated-list-revert))
  1079  
  1080  (defun govc-pool-info ()
  1081    "Wrapper for govc pool.info."
  1082    (govc-table-info "pool.info" (list "-a" (or govc-filter (setq govc-filter "*")))))
  1083  
  1084  (defun govc-pool-json-info ()
  1085    "JSON via govc pool.info -json on current selection."
  1086    (interactive)
  1087    (govc-json-info-selection "pool.info"))
  1088  
  1089  (defvar govc-pool-mode-map
  1090    (let ((map (make-sparse-keymap)))
  1091      (define-key map "D" 'govc-pool-destroy-selection)
  1092      (define-key map "E" 'govc-events)
  1093      (define-key map "J" 'govc-pool-json-info)
  1094      (define-key map "M" 'govc-metric)
  1095      (define-key map "O" 'govc-object-info)
  1096      (define-key map "T" 'govc-tasks)
  1097      (define-key map "c" 'govc-mode-new-session)
  1098      (define-key map "h" 'govc-host-with-session)
  1099      (define-key map "s" 'govc-datastore-with-session)
  1100      (define-key map "v" 'govc-vm-with-session)
  1101      (define-key map "?" 'govc-pool-popup)
  1102      map)
  1103    "Keymap for `govc-pool-mode'.")
  1104  
  1105  (defun govc-pool (&optional filter session)
  1106    "Pool info via govc.
  1107  Optionally filter by FILTER and inherit SESSION."
  1108    (interactive)
  1109    (let ((buffer (get-buffer-create "*govc-pool*")))
  1110      (pop-to-buffer buffer)
  1111      (govc-pool-mode)
  1112      (if session
  1113          (govc-session-clone session)
  1114        (call-interactively 'govc-session))
  1115      (setq govc-filter filter)
  1116      (tabulated-list-print)))
  1117  
  1118  (define-derived-mode govc-pool-mode govc-tabulated-list-mode "Pool"
  1119    "Major mode for handling a list of govc pools."
  1120    (setq tabulated-list-format [("Name" 30 t)
  1121                                 ("CPU Usage" 25 t)
  1122                                 ("CPU Shares" 25 t)
  1123                                 ("CPU Reservation" 25 t)
  1124                                 ("CPU Limit" 10 t)
  1125                                 ("Mem Usage" 25 t)
  1126                                 ("Mem Shares" 25 t)
  1127                                 ("Mem Reservation" 25 t)
  1128                                 ("Mem Limit" 10 t)]
  1129          tabulated-list-sort-key (cons "Name" nil)
  1130          tabulated-list-padding 2
  1131          tabulated-list-entries #'govc-pool-info)
  1132    (tabulated-list-init-header))
  1133  
  1134  (magit-define-popup govc-pool-popup
  1135    "Pool popup."
  1136    :actions (govc-keymap-popup govc-pool-mode-map))
  1137  
  1138  (easy-menu-define govc-host-mode-menu govc-pool-mode-map
  1139    "Pool menu."
  1140    (cons "Pool" (govc-keymap-menu govc-pool-mode-map)))
  1141  
  1142  
  1143  ;;; govc datastore mode
  1144  (defun govc-ls-datastore ()
  1145    "List datastores."
  1146    (govc "ls" "datastore"))
  1147  
  1148  (defun govc-datastore-ls-entries ()
  1149    "Wrapper for govc datastore.ls."
  1150    (let* ((data (govc-json "datastore.ls" "-l" "-p" govc-filter))
  1151           (file (plist-get (elt data 0) :file)))
  1152      (-map (lambda (ent)
  1153              (let ((name (plist-get ent :path))
  1154                    (size (or (plist-get ent :fileSize) 0))
  1155                    (time (plist-get ent :modification))
  1156                    (user (plist-get ent :owner)))
  1157                (list (concat govc-filter name)
  1158                      (vector (file-size-human-readable size)
  1159                              (current-time-string (date-to-time time))
  1160                              name)))) file)))
  1161  
  1162  (defun govc-datastore-ls-parent ()
  1163    "Up to parent folder."
  1164    (interactive)
  1165    (if (s-blank? govc-filter)
  1166        (let ((session (govc-current-session)))
  1167          (govc-datastore-mode)
  1168          (govc-session-clone session))
  1169      (setq govc-filter (file-name-directory (directory-file-name govc-filter))))
  1170    (tabulated-list-revert))
  1171  
  1172  (defun govc-datastore-ls-child ()
  1173    "Open datastore folder or file."
  1174    (interactive)
  1175    (let ((id (tabulated-list-get-id)))
  1176      (if current-prefix-arg
  1177          (govc-shell-command (list "datastore.ls" "-l" "-p" "-R" id))
  1178        (if (s-ends-with? "/" id)
  1179            (progn (setq govc-filter id)
  1180                   (tabulated-list-revert))
  1181          (govc-datastore-open)))))
  1182  
  1183  (defun govc-datastore-open ()
  1184    "Open datastore file."
  1185    (lexical-let* ((srcfile (tabulated-list-get-id))
  1186                   (srcpath (format "[%s] %s" (file-name-nondirectory govc-session-datastore) (s-chop-prefix "/" srcfile)))
  1187                   (suffix (file-name-extension srcfile t))
  1188                   (tmpfile (make-temp-file "govc-ds" nil suffix))
  1189                   (session (govc-current-session)))
  1190      (when (yes-or-no-p (concat "Open " srcpath "?"))
  1191        (govc "datastore.download" srcfile tmpfile)
  1192        (with-current-buffer (pop-to-buffer (find-file-noselect tmpfile))
  1193          (govc-session-clone session)
  1194          (add-hook 'kill-buffer-hook (lambda ()
  1195                                        (with-demoted-errors
  1196                                            (delete-file tmpfile))) t t)
  1197          (add-hook 'after-save-hook (lambda ()
  1198                                       (if (yes-or-no-p (concat "Upload changes to " srcpath "?"))
  1199                                           (with-demoted-errors
  1200                                               (govc "datastore.upload" tmpfile srcfile)))) t t)))))
  1201  
  1202  (defun govc-datastore-tail (&optional file)
  1203    "Tail datastore FILE."
  1204    (interactive)
  1205    (govc-shell-command
  1206     (list "datastore.tail" "-n" govc-max-events (if current-prefix-arg "-f") (or file (govc-selection)))))
  1207  
  1208  (defun govc-datastore-disk-info ()
  1209    "Info datastore disk."
  1210    (interactive)
  1211    (delete-other-windows)
  1212    (govc-shell-command
  1213     (list "datastore.disk.info" "-uuid" (if current-prefix-arg "-c") (govc-selection))))
  1214  
  1215  (defun govc-datastore-ls-json ()
  1216    "JSON via govc datastore.ls -json on current selection."
  1217    (interactive)
  1218    (let ((govc-args '("-l" "-p")))
  1219      (govc-json-info-selection "datastore.ls")))
  1220  
  1221  (defun govc-datastore-ls-r-json ()
  1222    "Search via govc datastore.ls -json -R on current selection."
  1223    (interactive)
  1224    (let ((govc-args '("-l" "-p" "-R")))
  1225      (govc-json-info-selection "datastore.ls")))
  1226  
  1227  (defun govc-datastore-mkdir (name)
  1228    "Mkdir via govc datastore.mkdir with given NAME."
  1229    (interactive (list (read-from-minibuffer "Create directory: " govc-filter)))
  1230    (govc "datastore.mkdir" name)
  1231    (tabulated-list-revert))
  1232  
  1233  (defun govc-datastore-rm (paths)
  1234    "Delete datastore PATHS."
  1235    (--each paths (govc "datastore.rm" (if current-prefix-arg "-f") it)))
  1236  
  1237  (defun govc-datastore-rm-selection ()
  1238    "Delete selected datastore paths."
  1239    (interactive)
  1240    (govc-do-selection 'govc-datastore-rm "Delete")
  1241    (tabulated-list-revert))
  1242  
  1243  (defvar govc-datastore-ls-mode-map
  1244    (let ((map (make-sparse-keymap)))
  1245      (define-key map "I" 'govc-datastore-disk-info)
  1246      (define-key map "J" 'govc-datastore-ls-json)
  1247      (define-key map "S" 'govc-datastore-ls-r-json)
  1248      (define-key map "D" 'govc-datastore-rm-selection)
  1249      (define-key map "T" 'govc-datastore-tail)
  1250      (define-key map "+" 'govc-datastore-mkdir)
  1251      (define-key map (kbd "DEL") 'govc-datastore-ls-parent)
  1252      (define-key map (kbd "RET") 'govc-datastore-ls-child)
  1253      (define-key map "?" 'govc-datastore-ls-popup)
  1254      map)
  1255    "Keymap for `govc-datastore-ls-mode'.")
  1256  
  1257  (defun govc-datastore-ls (&optional datastore session filter)
  1258    "List govc datastore.  Optionally specify DATASTORE, SESSION and FILTER."
  1259    (interactive)
  1260    (let ((buffer (get-buffer-create "*govc-datastore*")))
  1261      (pop-to-buffer buffer)
  1262      (govc-datastore-ls-mode)
  1263      (if session
  1264          (govc-session-clone session)
  1265        (call-interactively 'govc-session))
  1266      (setq govc-session-datastore (or datastore (govc-object-prompt "govc datastore: " 'govc-ls-datastore)))
  1267      (setq govc-filter filter)
  1268      (tabulated-list-print)))
  1269  
  1270  (define-derived-mode govc-datastore-ls-mode govc-tabulated-list-mode "Datastore"
  1271    "Major mode govc datastore.ls."
  1272    (setq-local font-lock-defaults `(,(cdr govc-font-lock-keywords)))
  1273    (setq tabulated-list-format [("Size" 10 t)
  1274                                 ("Modification time" 25 t)
  1275                                 ("Name" 40 t)]
  1276          tabulated-list-sort-key (cons "Name" nil)
  1277          tabulated-list-padding 2
  1278          tabulated-list-entries #'govc-datastore-ls-entries)
  1279    (tabulated-list-init-header))
  1280  
  1281  (magit-define-popup govc-datastore-ls-popup
  1282    "Datastore ls popup."
  1283    :actions (govc-keymap-popup govc-datastore-ls-mode-map))
  1284  
  1285  (easy-menu-define govc-datastore-ls-mode-menu govc-datastore-ls-mode-map
  1286    "Datastore ls menu."
  1287    (cons "Datastore" (govc-keymap-menu govc-datastore-ls-mode-map)))
  1288  
  1289  (defvar govc-datastore-mode-map
  1290    (let ((map (make-sparse-keymap)))
  1291      (define-key map "J" 'govc-datastore-json-info)
  1292      (define-key map "M" 'govc-metric)
  1293      (define-key map "O" 'govc-object-info)
  1294      (define-key map (kbd "RET") 'govc-datastore-ls-selection)
  1295      (define-key map "c" 'govc-mode-new-session)
  1296      (define-key map "h" 'govc-host-with-session)
  1297      (define-key map "p" 'govc-pool-with-session)
  1298      (define-key map "v" 'govc-vm-with-session)
  1299      (define-key map "?" 'govc-datastore-popup)
  1300      map)
  1301    "Keymap for `govc-datastore-mode'.")
  1302  
  1303  (defun govc-datastore-json-info ()
  1304    "JSON via govc datastore.info -json on current selection."
  1305    (interactive)
  1306    (govc-json-info-selection "datastore.info"))
  1307  
  1308  (defun govc-datastore-info ()
  1309    "Wrapper for govc datastore.info."
  1310    (govc-table-info "datastore.info" (or govc-filter "*")))
  1311  
  1312  (defun govc-datastore-ls-selection ()
  1313    "Browse datastore."
  1314    (interactive)
  1315    (govc-datastore-ls (tabulated-list-get-id) (govc-current-session)))
  1316  
  1317  (defun govc-datastore (&optional filter session)
  1318    "Datastore info via govc.
  1319  Optionally filter by FILTER and inherit SESSION."
  1320    (interactive)
  1321    (let ((buffer (get-buffer-create "*govc-datastore*")))
  1322      (pop-to-buffer buffer)
  1323      (govc-datastore-mode)
  1324      (if session
  1325          (govc-session-clone session)
  1326        (call-interactively 'govc-session))
  1327      (setq govc-filter filter)
  1328      (tabulated-list-print)
  1329      (if (and govc-session-datastore (search-forward govc-session-datastore nil t))
  1330          (beginning-of-line))))
  1331  
  1332  (define-derived-mode govc-datastore-mode tabulated-list-mode "Datastore"
  1333    "Major mode for govc datastore.info."
  1334    (setq tabulated-list-format [("Name" 15 t)
  1335                                 ("Type" 10 t)
  1336                                 ("Capacity" 10 t)
  1337                                 ("Free" 10 t)
  1338                                 ("Remote" 30 t)]
  1339          tabulated-list-sort-key (cons "Name" nil)
  1340          tabulated-list-padding 2
  1341          tabulated-list-entries #'govc-datastore-info)
  1342    (tabulated-list-init-header))
  1343  
  1344  (magit-define-popup govc-datastore-popup
  1345    "Datastore popup."
  1346    :actions (govc-keymap-popup govc-datastore-mode-map))
  1347  
  1348  (easy-menu-define govc-datastore-mode-menu govc-datastore-mode-map
  1349    "Datastore menu."
  1350    (cons "Datastore" (govc-keymap-menu govc-datastore-mode-map)))
  1351  
  1352  
  1353  ;;; govc vm mode
  1354  (defun govc-vm-prompt (prompt)
  1355    "PROMPT for a vm name."
  1356    (completing-read prompt (govc "ls" "vm")))
  1357  
  1358  (defun govc-vm-start (name)
  1359    "Start vm with given NAME."
  1360    (interactive (list (govc-vm-prompt "Start vm: ")))
  1361    (govc "vm.power" "-on" name))
  1362  
  1363  (defun govc-vm-shutdown (name)
  1364    "Shutdown vm with given NAME."
  1365    (interactive (list (govc-vm-prompt "Shutdown vm: ")))
  1366    (govc "vm.power" "-s" "-force" name))
  1367  
  1368  (defun govc-vm-reboot (name)
  1369    "Reboot vm with given NAME."
  1370    (interactive (list (govc-vm-prompt "Reboot vm: ")))
  1371    (govc "vm.power" "-r" "-force" name))
  1372  
  1373  (defun govc-vm-suspend (name)
  1374    "Suspend vm with given NAME."
  1375    (interactive (list (govc-vm-prompt "Suspend vm: ")))
  1376    (govc "vm.power" "-suspend" name))
  1377  
  1378  (defun govc-vm-destroy (name)
  1379    "Destroy vm with given NAME."
  1380    (interactive (list (govc-vm-prompt "Destroy vm: ")))
  1381    (govc "vm.destroy" name))
  1382  
  1383  (defun govc-vm-vnc-enable (name)
  1384    "Enable vnc on vm with given NAME."
  1385    (--map (last (split-string it))
  1386           (govc "vm.vnc" "-enable"
  1387                 "-port" "-1"
  1388                 "-password" (format "%08x" (random (expt 16 8))) name)))
  1389  
  1390  (defun govc-vm-vnc (name &optional arg)
  1391    "VNC for vm with given NAME.
  1392  By default, enable and open VNC for the given vm NAME.
  1393  With prefix \\[negative-argument] ARG, VNC will be disabled.
  1394  With prefix \\[universal-argument] ARG, VNC will be enabled but not opened."
  1395    (interactive (list (govc-vm-prompt "VNC vm: ")
  1396                       current-prefix-arg))
  1397    (if (equal arg '-)
  1398        (govc "vm.vnc" "-disable" name)
  1399      (let ((urls (govc-vm-vnc-enable name)))
  1400        (unless arg
  1401          (-each (-flatten urls) 'browse-url)))))
  1402  
  1403  (defun govc-vm-console (name &optional arg)
  1404    "Console for vm with given NAME.
  1405  By default, displays a console screen capture.
  1406  With prefix \\[universal-argument] ARG, launches an interactive console (VMRC)."
  1407    (interactive (list (govc-vm-prompt "Console vm: ")
  1408                       current-prefix-arg))
  1409    (if arg
  1410        (browse-url (car (govc "vm.console" name)))
  1411      (let* ((data (govc-process (govc-format-command "vm.console" "-capture" "-" name) 'buffer-string))
  1412             (inhibit-read-only t))
  1413        (with-current-buffer (get-buffer-create "*govc*")
  1414          (erase-buffer)
  1415          (insert-image (create-image (string-as-unibyte data) 'png t))
  1416          (read-only-mode)
  1417          (display-buffer (current-buffer))))))
  1418  
  1419  (defun govc-vm-start-selection ()
  1420    "Start via `govc-vm-start' on the current selection."
  1421    (interactive)
  1422    (govc-vm-start (govc-selection))
  1423    (tabulated-list-revert))
  1424  
  1425  (defun govc-vm-shutdown-selection ()
  1426    "Shutdown via `govc-vm-shutdown' on the current selection."
  1427    (interactive)
  1428    (govc-vm-shutdown (govc-selection))
  1429    (tabulated-list-revert))
  1430  
  1431  (defun govc-vm-reboot-selection ()
  1432    "Reboot via `govc-vm-reboot' on the current selection."
  1433    (interactive)
  1434    (govc-vm-reboot (govc-selection))
  1435    (tabulated-list-revert))
  1436  
  1437  (defun govc-vm-suspend-selection ()
  1438    "Suspend via `govc-vm-suspend' on the current selection."
  1439    (interactive)
  1440    (govc-vm-suspend (govc-selection))
  1441    (tabulated-list-revert))
  1442  
  1443  (defun govc-vm-destroy-selection ()
  1444    "Destroy via `govc-vm-destroy' on the current selection."
  1445    (interactive)
  1446    (govc-do-selection 'govc-vm-destroy "Destroy")
  1447    (tabulated-list-revert))
  1448  
  1449  (defun govc-vm-vnc-selection ()
  1450    "VNC via `govc-vm-vnc' on the current selection."
  1451    (interactive)
  1452    (govc-vm-vnc (govc-selection) current-prefix-arg))
  1453  
  1454  (defun govc-vm-console-selection ()
  1455    "Console via `govc-vm-console' on the current selection."
  1456    (interactive)
  1457    (govc-vm-console (tabulated-list-get-id) current-prefix-arg))
  1458  
  1459  (defun govc-vm-info ()
  1460    "Wrapper for govc vm.info."
  1461    (unless (string-empty-p govc-session-datacenter)
  1462      (govc-table-info "vm.info" (list "-r" (or govc-filter (setq govc-filter "*"))))))
  1463  
  1464  (defun govc-vm-host ()
  1465    "Host info via `govc-host' with host(s) of current selection."
  1466    (interactive)
  1467    (govc-host (concat "*/" (govc-table-column-value "Host"))
  1468               (govc-current-session)))
  1469  
  1470  (defun govc-vm-log-directory ()
  1471    "VM log directory of current selection."
  1472    (car (govc "object.collect" "-s" (tabulated-list-get-id) "config.files.logDirectory")))
  1473  
  1474  (defun govc-vm-datastore ()
  1475    "Datastore via `govc-datastore-ls' with datastore of current selection."
  1476    (interactive)
  1477    (if current-prefix-arg
  1478        (govc-datastore (s-split ", " (govc-table-column-value "Storage") t)
  1479                        (govc-current-session))
  1480      (let* ((dir (govc-vm-log-directory))
  1481             (args (s-split "\\[\\|\\]" dir t)))
  1482        (govc-datastore-ls (first args) (govc-current-session) (concat (s-trim (second args)) "/")))))
  1483  
  1484  (defun govc-vm-logs ()
  1485    "Logs via `govc-datastore-tail' with logDirectory of current selection."
  1486    (interactive)
  1487    (if (tabulated-list-get-id)
  1488        (govc-datastore-tail (concat (govc-vm-log-directory) "/vmware.log"))
  1489      (govc-logs)))
  1490  
  1491  (defun govc-vm-ping ()
  1492    "Ping VM."
  1493    (interactive)
  1494    (let ((ping-program-options '("-c" "20")))
  1495      (ping (govc-table-column-value "IP address"))))
  1496  
  1497  (defun govc-vm-device-ls ()
  1498    "Devices via `govc-device' on the current selection."
  1499    (interactive)
  1500    (govc-device (tabulated-list-get-id)
  1501                 (govc-current-session)))
  1502  
  1503  (defun govc-vm-extra-config ()
  1504    "Populate table with govc vm.info -e output."
  1505    (let* ((data (govc-json "vm.info" govc-args))
  1506           (vms (plist-get data :virtualMachines))
  1507           (info))
  1508      (mapc
  1509       (lambda (vm)
  1510         (let* ((config (plist-get vm :config))
  1511                (name (plist-get config :name)))
  1512           (mapc (lambda (x)
  1513                   (let ((key (plist-get x :key))
  1514                         (val (plist-get x :value)))
  1515                     (push (list key (vector key val)) info)))
  1516                 (plist-get config :extraConfig))
  1517           (if (> (length vms) 1)
  1518               (push (list name (vector "vm.name" name)) info))))
  1519       vms)
  1520      info))
  1521  
  1522  (defun govc-vm-extra-config-table ()
  1523    "ExtraConfig via `govc-vm-extra-config' on the current selection."
  1524    (interactive)
  1525    (govc-map-info-table #'govc-vm-extra-config))
  1526  
  1527  (defun govc-vm-json-info ()
  1528    "JSON via govc vm.info -json on current selection."
  1529    (interactive)
  1530    (govc-json-info-selection "vm.info"))
  1531  
  1532  (defvar govc-vm-mode-map
  1533    (let ((map (make-sparse-keymap)))
  1534      (define-key map "E" 'govc-events)
  1535      (define-key map "L" 'govc-vm-logs)
  1536      (define-key map "J" 'govc-vm-json-info)
  1537      (define-key map "O" 'govc-object-info)
  1538      (define-key map "T" 'govc-tasks)
  1539      (define-key map "X" 'govc-vm-extra-config-table)
  1540      (define-key map (kbd "RET") 'govc-vm-device-ls)
  1541      (define-key map "C" 'govc-vm-console-selection)
  1542      (define-key map "V" 'govc-vm-vnc-selection)
  1543      (define-key map "D" 'govc-vm-destroy-selection)
  1544      (define-key map "^" 'govc-vm-start-selection)
  1545      (define-key map "!" 'govc-vm-shutdown-selection)
  1546      (define-key map "@" 'govc-vm-reboot-selection)
  1547      (define-key map "&" 'govc-vm-suspend-selection)
  1548      (define-key map "H" 'govc-vm-host)
  1549      (define-key map "M" 'govc-metric)
  1550      (define-key map "P" 'govc-vm-ping)
  1551      (define-key map "S" 'govc-vm-datastore)
  1552      (define-key map "c" 'govc-mode-new-session)
  1553      (define-key map "h" 'govc-host-with-session)
  1554      (define-key map "p" 'govc-pool-with-session)
  1555      (define-key map "s" 'govc-datastore-with-session)
  1556      (define-key map "?" 'govc-vm-popup)
  1557      map)
  1558    "Keymap for `govc-vm-mode'.")
  1559  
  1560  (defun govc-vm (&optional filter session)
  1561    "VM info via govc.
  1562  Optionally filter by FILTER and inherit SESSION."
  1563    (interactive)
  1564    (let ((buffer (get-buffer-create "*govc-vm*")))
  1565      (pop-to-buffer buffer)
  1566      (govc-vm-mode)
  1567      (if session
  1568          (govc-session-clone session)
  1569        (call-interactively 'govc-session))
  1570      (setq govc-filter filter)
  1571      (tabulated-list-print)))
  1572  
  1573  (define-derived-mode govc-vm-mode govc-tabulated-list-mode "VM"
  1574    "Major mode for handling a list of govc vms."
  1575    (setq tabulated-list-format [("Name" 40 t)
  1576                                 ("Power state" 12 t)
  1577                                 ("Boot time" 13 t)
  1578                                 ("IP address" 15 t)
  1579                                 ("Guest name" 20 t)
  1580                                 ("Host" 20 t)
  1581                                 ("CPU usage" 15 t)
  1582                                 ("Host memory usage" 18 t)
  1583                                 ("Guest memory usage" 19 t)
  1584                                 ("Storage committed" 18 t)
  1585                                 ("Storage" 10 t)
  1586                                 ("Network" 10 t)]
  1587          tabulated-list-sort-key (cons "Name" nil)
  1588          tabulated-list-padding 2
  1589          tabulated-list-entries #'govc-vm-info)
  1590    (tabulated-list-init-header))
  1591  
  1592  (magit-define-popup govc-vm-popup
  1593    "VM popup."
  1594    :actions (govc-keymap-popup govc-vm-mode-map))
  1595  
  1596  (easy-menu-define govc-vm-mode-menu govc-vm-mode-map
  1597    "VM menu."
  1598    (cons "VM" (govc-keymap-menu govc-vm-mode-map)))
  1599  
  1600  
  1601  ;;; govc device mode
  1602  (defun govc-device-ls ()
  1603    "Wrapper for govc device.ls -vm VM."
  1604    (govc-type-list-entries "device.ls"))
  1605  
  1606  (defun govc-device-info ()
  1607    "Populate table with govc device.info output."
  1608    (govc-map-info "device.info" govc-args))
  1609  
  1610  (defun govc-device-info-table ()
  1611    "Tabulated govc device.info."
  1612    (interactive)
  1613    (govc-map-info-table #'govc-device-info))
  1614  
  1615  (defun govc-device-json-info ()
  1616    "JSON via govc device.info -json on current selection."
  1617    (interactive)
  1618    (govc-json-info-selection "device.info"))
  1619  
  1620  (defvar govc-device-mode-map
  1621    (let ((map (make-sparse-keymap)))
  1622      (define-key map (kbd "J") 'govc-device-json-info)
  1623      (define-key map (kbd "RET") 'govc-device-info-table)
  1624      map)
  1625    "Keymap for `govc-device-mode'.")
  1626  
  1627  (defun govc-device (&optional vm session)
  1628    "List govc devices for VM.  Optionally inherit SESSION."
  1629    (interactive)
  1630    (let ((buffer (get-buffer-create "*govc-device*")))
  1631      (pop-to-buffer buffer)
  1632      (govc-device-mode)
  1633      (if session
  1634          (govc-session-clone session)
  1635        (call-interactively 'govc-session))
  1636      (setq govc-args (list "-vm" (or vm (govc-vm-prompt "vm: "))))
  1637      (tabulated-list-print)))
  1638  
  1639  (define-derived-mode govc-device-mode govc-tabulated-list-mode "Device"
  1640    "Major mode for handling a govc device."
  1641    (setq tabulated-list-format [("Name" 15 t)
  1642                                 ("Type" 30 t)
  1643                                 ("Summary" 40 t)]
  1644          tabulated-list-sort-key (cons "Name" nil)
  1645          tabulated-list-padding 2
  1646          tabulated-list-entries #'govc-device-ls)
  1647    (tabulated-list-init-header))
  1648  
  1649  (magit-define-popup govc-popup
  1650    "govc popup."
  1651    :actions (govc-keymap-list govc-command-map))
  1652  
  1653  (easy-menu-change
  1654   '("Tools") "govc"
  1655   (govc-keymap-menu govc-command-map)
  1656   "Search Files (Grep)...")
  1657  
  1658  (provide 'govc)
  1659  
  1660  ;;; govc.el ends here