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