Personal emacs config
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

532 lines
20 KiB

  1. ;;; docker-container.el --- Emacs interface to docker-container -*- lexical-binding: t -*-
  2. ;; Author: Philippe Vaucher <philippe.vaucher@gmail.com>
  3. ;; Yuki Inoue <inouetakahiroki@gmail.com>
  4. ;; This file is NOT part of GNU Emacs.
  5. ;; This program is free software; you can redistribute it and/or modify
  6. ;; it under the terms of the GNU General Public License as published by
  7. ;; the Free Software Foundation; either version 3, or (at your option)
  8. ;; any later version.
  9. ;;
  10. ;; This program is distributed in the hope that it will be useful,
  11. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ;; GNU General Public License for more details.
  14. ;;
  15. ;; You should have received a copy of the GNU General Public License
  16. ;; along with GNU Emacs; see the file COPYING. If not, write to the
  17. ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  18. ;; Boston, MA 02110-1301, USA.
  19. ;;; Commentary:
  20. ;;; Code:
  21. (require 's)
  22. (require 'dash)
  23. (require 'json)
  24. (require 'tablist)
  25. (require 'magit-popup)
  26. (require 'docker-group)
  27. (require 'docker-process)
  28. (require 'docker-utils)
  29. (defgroup docker-container nil
  30. "Docker container customization group."
  31. :group 'docker)
  32. (defcustom docker-container-ls-arguments '("--all")
  33. "Default arguments for `docker-container-ls-popup'."
  34. :group 'docker-container
  35. :type '(repeat (string :tag "Argument")))
  36. (defcustom docker-container-shell-file-name shell-file-name
  37. "Shell to use when entering containers.
  38. For more information see the variable `shell-file-name'."
  39. :group 'docker-container
  40. :type 'string)
  41. (defcustom docker-container-default-sort-key '("Image" . nil)
  42. "Sort key for docker containers.
  43. This should be a cons cell (NAME . FLIP) where
  44. NAME is a string matching one of the column names
  45. and FLIP is a boolean to specify the sort order."
  46. :group 'docker-container
  47. :type '(cons (choice (const "Id")
  48. (const "Image")
  49. (const "Command")
  50. (const "Created")
  51. (const "Status")
  52. (const "Ports")
  53. (const "Names"))
  54. (choice (const :tag "Ascending" nil)
  55. (const :tag "Descending" t))))
  56. (defun docker-container--read-shell (&optional read-shell-name)
  57. "Reads a shell name if `read-shell-name' is truthy."
  58. (if read-shell-name (read-shell-command "Shell: ") docker-container-shell-file-name))
  59. (defun docker-container-parse (line)
  60. "Convert a LINE from \"docker container ls\" to a `tabulated-list-entries' entry."
  61. (condition-case nil
  62. (let ((data (json-read-from-string line)))
  63. (setf (aref data 3) (format-time-string "%F %T" (date-to-time (aref data 3))))
  64. (list (aref data 6) data))
  65. (json-readtable-error
  66. (error "Could not read following string as json:\n%s" line))))
  67. (defun docker-container-entries ()
  68. "Return the docker containers data for `tabulated-list-entries'."
  69. (let* ((fmt (if(docker-utils-podman-p) "json" "[{{json .ID}},{{json .Image}},{{json .Command}},{{json .CreatedAt}},{{json .Status}},{{json .Ports}},{{json .Names}}]"))
  70. (data (docker-run "container ls" docker-container-ls-arguments (format "--format=\"%s\"" fmt)))
  71. (lines (s-split "\n" data t)))
  72. (-map #'docker-container-parse lines)))
  73. (defun docker-container-refresh ()
  74. "Refresh the containers list."
  75. (setq tabulated-list-entries (docker-container-entries)))
  76. (defun docker-container-read-name ()
  77. "Read an container name."
  78. (completing-read "Container: " (-map #'car (docker-container-entries))))
  79. ;;;###autoload
  80. (defun docker-container-attach (container args)
  81. "Run \"docker attach ARGS CONTAINER\"."
  82. (interactive (list (docker-compose-read-name) (docker-container-attach-arguments)))
  83. (docker-run "attach" args container))
  84. ;;;###autoload
  85. (defun docker-container-eshell (container)
  86. "Open `eshell' in CONTAINER."
  87. (interactive (list (docker-container-read-name)))
  88. (let* ((container-address (format "docker:%s:/" container))
  89. (file-prefix (let ((prefix (file-remote-p default-directory)))
  90. (if prefix
  91. (format "%s|" (s-chop-suffix ":" prefix))
  92. "/")))
  93. (default-directory (format "%s%s" file-prefix container-address))
  94. (eshell-buffer-name (generate-new-buffer-name (format "*eshell %s*" default-directory))))
  95. (eshell)))
  96. ;;;###autoload
  97. (defun docker-container-find-directory (container directory)
  98. "Inside CONTAINER open DIRECTORY."
  99. (interactive
  100. (let* ((container-name (docker-container-read-name))
  101. (tramp-filename (read-directory-name "Directory: " (format "/docker:%s:/" container-name))))
  102. (with-parsed-tramp-file-name tramp-filename nil
  103. (list host localname))))
  104. (dired (format "/docker:%s:%s" container directory)))
  105. (defalias 'docker-container-dired 'docker-container-find-directory)
  106. ;;;###autoload
  107. (defun docker-container-find-file (container file)
  108. "Inside CONTAINER open FILE."
  109. (interactive
  110. (let* ((container-name (docker-container-read-name))
  111. (tramp-filename (read-file-name "File: " (format "/docker:%s:/" container-name))))
  112. (with-parsed-tramp-file-name tramp-filename nil
  113. (list host localname))))
  114. (find-file (format "/docker:%s:%s" container file)))
  115. ;;;###autoload
  116. (defun docker-container-shell (container &optional read-shell)
  117. "Open `shell' in CONTAINER."
  118. (interactive (list
  119. (docker-container-read-name)
  120. current-prefix-arg))
  121. (let* ((shell-file-name (docker-container--read-shell read-shell))
  122. (container-address (format "docker:%s:/" container))
  123. (file-prefix (let ((prefix (file-remote-p default-directory)))
  124. (if prefix
  125. (format "%s|" (s-chop-suffix ":" prefix))
  126. "/")))
  127. (default-directory (format "%s%s" file-prefix container-address)))
  128. (shell (generate-new-buffer (format "*shell %s*" default-directory)))))
  129. ;;;###autoload
  130. (defun docker-diff (name)
  131. "Diff the container named NAME."
  132. (interactive (list (docker-container-read-name)))
  133. (docker-utils-with-buffer (format "diff %s" name)
  134. (insert (docker-run "diff" name))))
  135. ;;;###autoload
  136. (defun docker-inspect (name)
  137. "Inspect the container named NAME."
  138. (interactive (list (docker-container-read-name)))
  139. (docker-utils-with-buffer (format "inspect %s" name)
  140. (insert (docker-run "inspect" name))
  141. (json-mode)))
  142. ;;;###autoload
  143. (defun docker-kill (name &optional signal)
  144. "Kill the container named NAME using SIGNAL."
  145. (interactive (list (docker-container-read-name)))
  146. (docker-run "kill" (when signal (format "-s %s" signal)) name))
  147. ;;;###autoload
  148. (defun docker-logs (name &optional follow)
  149. "Show the logs from container NAME.
  150. If FOLLOW is set, run in `async-shell-command'."
  151. (interactive (list (docker-container-read-name)))
  152. (if follow
  153. (async-shell-command
  154. (format "%s logs -f %s" docker-command name)
  155. (generate-new-buffer (format "* docker logs %s *" name)))
  156. (docker-utils-with-buffer (format "logs %s" name)
  157. (insert (docker-run "logs" name)))))
  158. ;;;###autoload
  159. (defun docker-pause (name)
  160. "Pause the container named NAME."
  161. (interactive (list (docker-container-read-name)))
  162. (docker-run "pause" name))
  163. ;;;###autoload
  164. (defun docker-rename (container name)
  165. "Rename CONTAINER using NAME."
  166. (interactive (list (docker-container-read-name) (read-string "Name: ")))
  167. (docker-run "rename" container name))
  168. ;;;###autoload
  169. (defun docker-restart (name &optional timeout)
  170. "Restart the container named NAME.
  171. TIMEOUT is the number of seconds to wait for the container to stop before killing it."
  172. (interactive (list (docker-container-read-name) current-prefix-arg))
  173. (docker-run "restart" (when timeout (format "-t %d" timeout)) name))
  174. ;;;###autoload
  175. (defun docker-rm (name &optional force link volumes)
  176. "Remove the container named NAME.
  177. With prefix argument, sets FORCE to true.
  178. Force the removal even if the container is running when FORCE is set.
  179. Remove the specified link and not the underlying container when LINK is set.
  180. Remove the volumes associated with the container when VOLUMES is set."
  181. (interactive (list (docker-container-read-name) current-prefix-arg))
  182. (docker-run "rm" (when force "-f") (when link "-l") (when volumes "-v") name))
  183. ;;;###autoload
  184. (defun docker-start (name)
  185. "Start the container named NAME."
  186. (interactive (list (docker-container-read-name)))
  187. (docker-run "start" name))
  188. ;;;###autoload
  189. (defun docker-stop (name &optional timeout)
  190. "Stop the container named NAME.
  191. TIMEOUT is the number of seconds to wait for the container to stop before killing it."
  192. (interactive (list (docker-container-read-name) current-prefix-arg))
  193. (docker-run "stop" (when timeout (format "-t %d" timeout)) name))
  194. ;;;###autoload
  195. (defun docker-unpause (name)
  196. "Unpause the container named NAME."
  197. (interactive (list (docker-container-read-name)))
  198. (docker-run "unpause" name))
  199. (defun docker-container-attach-selection ()
  200. "Run \"docker attach\" with the containers selection."
  201. (interactive)
  202. (let ((default-directory (if (and docker-run-as-root
  203. (not (file-remote-p default-directory)))
  204. "/sudo::"
  205. default-directory)))
  206. (--each (docker-utils-get-marked-items-ids)
  207. (async-shell-command
  208. (format "%s attach %s %s" docker-command (s-join " " (docker-container-attach-arguments)) it)
  209. (generate-new-buffer (format "*attach %s*" it))))))
  210. (defun docker-container-cp-from-selection (container-path host-path)
  211. "Run \"docker cp\" from CONTAINER-PATH to HOST-PATH for selected container."
  212. (interactive "sContainer path: \nFHost path: ")
  213. (--each (docker-utils-get-marked-items-ids)
  214. (docker-run "cp" (concat it ":" container-path) host-path)))
  215. (defun docker-container-cp-to-selection (host-path container-path)
  216. "Run \"docker cp\" from HOST-PATH to CONTAINER-PATH for selected containers."
  217. (interactive "fHost path: \nsContainer path: ")
  218. (--each (docker-utils-get-marked-items-ids)
  219. (docker-run "cp" host-path (concat it ":" container-path))))
  220. (defun docker-container-diff-selection ()
  221. "Run `docker-diff' on the containers selection."
  222. (interactive)
  223. (--each (docker-utils-get-marked-items-ids)
  224. (docker-utils-with-buffer (format "diff %s" it)
  225. (insert (docker-run "diff" (docker-container-diff-arguments) it)))))
  226. (defun docker-container-eshell-selection ()
  227. "Run `docker-container-eshell' on the containers selection."
  228. (interactive)
  229. (--each (docker-utils-get-marked-items-ids)
  230. (docker-container-eshell it)))
  231. (defun docker-container-find-file-selection (path)
  232. "Run `docker-container-find-file' for PATH on the containers selection."
  233. (interactive "sPath: ")
  234. (--each (docker-utils-get-marked-items-ids)
  235. (docker-container-find-file it path)))
  236. (defun docker-container-inspect-selection ()
  237. "Run `docker-inspect' on the containers selection."
  238. (interactive)
  239. (--each (docker-utils-get-marked-items-ids)
  240. (docker-utils-with-buffer (format "inspect %s" it)
  241. (insert (docker-run "inspect" (docker-container-inspect-arguments) it))
  242. (json-mode))))
  243. (defun docker-container-kill-selection ()
  244. "Run `docker-kill' on the containers selection."
  245. (interactive)
  246. (--each (docker-utils-get-marked-items-ids)
  247. (docker-run "kill" (docker-container-kill-arguments) it))
  248. (tablist-revert))
  249. (defun docker-container-logs-selection ()
  250. "Run \"docker logs\" on the containers selection."
  251. (interactive)
  252. (--each (docker-utils-get-marked-items-ids)
  253. (async-shell-command
  254. (format "%s logs %s %s" docker-command (s-join " " (docker-container-logs-arguments)) it)
  255. (generate-new-buffer (format "* docker logs %s *" it)))))
  256. (defun docker-container-pause-selection ()
  257. "Run `docker-pause' on the containers selection."
  258. (interactive)
  259. (--each (docker-utils-get-marked-items-ids)
  260. (docker-run "pause" (docker-container-pause-arguments) it))
  261. (tablist-revert))
  262. (defun docker-container-rename-selection ()
  263. "Rename containers."
  264. (interactive)
  265. (docker-utils-select-if-empty)
  266. (--each (docker-utils-get-marked-items-ids)
  267. (docker-rename it (read-string (format "New name for %s: " it))))
  268. (tablist-revert))
  269. (defun docker-container-restart-selection ()
  270. "Run `docker-restart' on the containers selection."
  271. (interactive)
  272. (--each (docker-utils-get-marked-items-ids)
  273. (docker-run "restart" (docker-container-restart-arguments) it))
  274. (tablist-revert))
  275. (defun docker-container-rm-selection ()
  276. "Run `docker-rm' on the containers selection."
  277. (interactive)
  278. (--each (docker-utils-get-marked-items-ids)
  279. (docker-run "rm" (docker-container-rm-arguments) it))
  280. (tablist-revert))
  281. (defun docker-container-shell-selection (prefix)
  282. "Run `docker-container-shell' on the containers selection."
  283. (interactive "P")
  284. (--each (docker-utils-get-marked-items-ids)
  285. (docker-container-shell it prefix)))
  286. (defun docker-container-start-selection ()
  287. "Run `docker-start' on the containers selection."
  288. (interactive)
  289. (--each (docker-utils-get-marked-items-ids)
  290. (docker-run "start" (docker-container-start-arguments) it))
  291. (tablist-revert))
  292. (defun docker-container-stop-selection ()
  293. "Run `docker-stop' on the containers selection."
  294. (interactive)
  295. (--each (docker-utils-get-marked-items-ids)
  296. (docker-run "stop" (docker-container-stop-arguments) it))
  297. (tablist-revert))
  298. (defun docker-container-unpause-selection ()
  299. "Run `docker-unpause' on the containers selection."
  300. (interactive)
  301. (--each (docker-utils-get-marked-items-ids)
  302. (docker-run "unpause" (docker-container-pause-arguments) it))
  303. (tablist-revert))
  304. (magit-define-popup docker-container-attach-popup
  305. "Popup for attaching to containers."
  306. 'docker-container
  307. :man-page "docker-attach"
  308. :switches '((?n "No STDIN" "--no-stdin"))
  309. :options '((?d "Key sequence for detaching" "--detach-keys "))
  310. :actions '((?a "Attach" docker-container-attach-selection))
  311. :setup-function #'docker-utils-setup-popup)
  312. (magit-define-popup docker-container-cp-popup
  313. "Popup for copying files from/to containers."
  314. 'docker-container
  315. :man-page "docker-cp"
  316. :actions '((?f "Copy From" docker-container-cp-from-selection)
  317. (?t "Copy To" docker-container-cp-to-selection))
  318. :setup-function #'docker-utils-setup-popup)
  319. (magit-define-popup docker-container-diff-popup
  320. "Popup for showing containers diffs."
  321. 'docker-container
  322. :man-page "docker-diff"
  323. :actions '((?d "Diff" docker-container-diff-selection))
  324. :setup-function #'docker-utils-setup-popup)
  325. (magit-define-popup docker-container-find-file-popup
  326. "Popup for opening containers files."
  327. 'docker-container
  328. :actions '((?f "Open file" docker-container-find-file-selection))
  329. :setup-function #'docker-utils-setup-popup)
  330. (magit-define-popup docker-container-inspect-popup
  331. "Popup for inspecting containers."
  332. 'docker-container
  333. :man-page "docker-inspect"
  334. :actions '((?I "Inspect" docker-container-inspect-selection))
  335. :setup-function #'docker-utils-setup-popup)
  336. (magit-define-popup docker-container-kill-popup
  337. "Popup for kill signaling containers"
  338. 'docker-container
  339. :man-page "docker-kill"
  340. :options '((?s "Signal" "-s "))
  341. :actions '((?K "Kill" docker-container-kill-selection))
  342. :setup-function #'docker-utils-setup-popup)
  343. (magit-define-popup docker-container-logs-popup
  344. "Popup for showing containers logs."
  345. 'docker-container
  346. :man-page "docker-logs"
  347. :switches '((?f "Follow" "-f"))
  348. :actions '((?L "Logs" docker-container-logs-selection))
  349. :setup-function #'docker-utils-setup-popup)
  350. (magit-define-popup docker-container-ls-popup
  351. "Popup for listing containers."
  352. 'docker-container
  353. :man-page "docker-container-ls"
  354. :switches '((?a "All" "--all")
  355. (?e "Exited containers" "--filter status=exited")
  356. (?n "Don't truncate" "--no-trunc"))
  357. :options '((?f "Filter" "--filter ")
  358. (?n "Last" "--last "))
  359. :actions `((?l "List" ,(docker-utils-set-then-call 'docker-container-ls-arguments 'tablist-revert))))
  360. (magit-define-popup docker-container-pause-popup
  361. "Popup for pauseing containers."
  362. 'docker-container
  363. :man-page "docker-pause"
  364. :actions '((?P "Pause" docker-container-pause-selection)
  365. (?U "Unpause" docker-container-unpause-selection))
  366. :setup-function #'docker-utils-setup-popup)
  367. (magit-define-popup docker-container-restart-popup
  368. "Popup for restarting containers."
  369. 'docker-container
  370. :man-page "docker-restart"
  371. :options '((?t "Timeout" "-t "))
  372. :actions '((?R "Restart" docker-container-restart-selection))
  373. :setup-function #'docker-utils-setup-popup)
  374. (magit-define-popup docker-container-rm-popup
  375. "Popup for removing containers."
  376. 'docker-container
  377. :man-page "docker-rm"
  378. :switches '((?f "Force" "-f")
  379. (?v "Volumes" "-v"))
  380. :actions '((?D "Remove" docker-container-rm-selection))
  381. :setup-function #'docker-utils-setup-popup)
  382. (magit-define-popup docker-container-shell-popup
  383. "Popup for doing M-x `shell'/`eshell' to containers."
  384. 'docker-container
  385. :actions '((?b "Shell" docker-container-shell-selection)
  386. (?e "Eshell" docker-container-eshell-selection))
  387. :setup-function #'docker-utils-setup-popup)
  388. (magit-define-popup docker-container-start-popup
  389. "Popup for starting containers."
  390. 'docker-container
  391. :man-page "docker-start"
  392. :actions '((?S "Start" docker-container-start-selection))
  393. :setup-function #'docker-utils-setup-popup)
  394. (magit-define-popup docker-container-stop-popup
  395. "Popup for stoping containers."
  396. 'docker-container
  397. :man-page "docker-stop"
  398. :options '((?t "Timeout" "-t "))
  399. :actions '((?O "Stop" docker-container-stop-selection))
  400. :setup-function #'docker-utils-setup-popup)
  401. (magit-define-popup docker-container-help-popup
  402. "Help popup for docker containers."
  403. 'docker-container
  404. :actions '("Docker containers help"
  405. (?C "Copy" docker-container-cp-popup)
  406. (?D "Remove" docker-container-rm-popup)
  407. (?I "Inspect" docker-container-inspect-popup)
  408. (?K "Kill" docker-container-kill-popup)
  409. (?L "Logs" docker-container-logs-popup)
  410. (?O "Stop" docker-container-stop-popup)
  411. (?P "Pause" docker-container-pause-popup)
  412. (?R "Restart" docker-container-restart-popup)
  413. (?S "Start" docker-container-start-popup)
  414. (?a "Attach" docker-container-attach-popup)
  415. (?b "Shell" docker-container-shell-popup)
  416. (?d "Diff" docker-container-diff-popup)
  417. (?f "Find file" docker-container-find-file-popup)
  418. (?l "List" docker-container-ls-popup)
  419. (?r "Rename" docker-container-rename-selection)))
  420. (defvar docker-container-mode-map
  421. (let ((map (make-sparse-keymap)))
  422. (define-key map "?" 'docker-container-help-popup)
  423. (define-key map "C" 'docker-container-cp-popup)
  424. (define-key map "D" 'docker-container-rm-popup)
  425. (define-key map "I" 'docker-container-inspect-popup)
  426. (define-key map "K" 'docker-container-kill-popup)
  427. (define-key map "L" 'docker-container-logs-popup)
  428. (define-key map "O" 'docker-container-stop-popup)
  429. (define-key map "P" 'docker-container-pause-popup)
  430. (define-key map "R" 'docker-container-restart-popup)
  431. (define-key map "S" 'docker-container-start-popup)
  432. (define-key map "a" 'docker-container-attach-popup)
  433. (define-key map "b" 'docker-container-shell-popup)
  434. (define-key map "d" 'docker-container-diff-popup)
  435. (define-key map "f" 'docker-container-find-file-popup)
  436. (define-key map "l" 'docker-container-ls-popup)
  437. (define-key map "r" 'docker-container-rename-selection)
  438. map)
  439. "Keymap for `docker-container-mode'.")
  440. ;;;###autoload
  441. (defun docker-containers ()
  442. "List docker containers."
  443. (interactive)
  444. (docker-utils-pop-to-buffer "*docker-containers*")
  445. (docker-container-mode)
  446. (tablist-revert))
  447. (define-derived-mode docker-container-mode tabulated-list-mode "Containers Menu"
  448. "Major mode for handling a list of docker containers."
  449. (setq tabulated-list-format [("Id" 16 t)("Image" 15 t)("Command" 30 t)("Created" 23 t)("Status" 20 t)("Ports" 10 t)("Names" 10 t)])
  450. (setq tabulated-list-padding 2)
  451. (setq tabulated-list-sort-key docker-container-default-sort-key)
  452. (add-hook 'tabulated-list-revert-hook 'docker-container-refresh nil t)
  453. (tabulated-list-init-header)
  454. (tablist-minor-mode))
  455. (provide 'docker-container)
  456. ;;; docker-container.el ends here