/ docker-compose.el
docker-compose.el
  1  ;;; docker-compose.el --- Interface to docker-compose  -*- lexical-binding: t -*-
  2  
  3  ;; Author: Philippe Vaucher <philippe.vaucher@gmail.com>
  4  
  5  ;; This file is NOT part of GNU Emacs.
  6  
  7  ;; This program is free software; you can redistribute it and/or modify
  8  ;; it under the terms of the GNU General Public License as published by
  9  ;; the Free Software Foundation; either version 3, or (at your option)
 10  ;; any later version.
 11  ;;
 12  ;; This program is distributed in the hope that it will be useful,
 13  ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
 14  ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15  ;; GNU General Public License for more details.
 16  ;;
 17  ;; You should have received a copy of the GNU General Public License
 18  ;; along with GNU Emacs; see the file COPYING.  If not, write to the
 19  ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 20  ;; Boston, MA 02110-1301, USA.
 21  
 22  ;;; Commentary:
 23  
 24  ;;; Code:
 25  (eval-when-compile
 26    (setq-local byte-compile-warnings '(not docstrings)))
 27  
 28  (require 's)
 29  (require 'aio)
 30  (require 'dash)
 31  (require 'transient)
 32  
 33  (require 'docker-group)
 34  (require 'docker-utils)
 35  (require 'docker-process)
 36  
 37  (defgroup docker-compose nil
 38    "Docker compose customization group."
 39    :group 'docker)
 40  
 41  (defcustom docker-compose-command "docker compose"
 42    "The `docker-compose' binary."
 43    :group 'docker-compose
 44    :type 'string)
 45  
 46  (defun docker-compose-run-docker-compose-async (action &rest args)
 47    "Execute \"`docker-compose-command' ACTION ARGS\" and return a promise with the results."
 48    (apply #'docker-run-async docker-compose-command (docker-compose-arguments) action args))
 49  
 50  (defun docker-compose-run-docker-compose-async-with-buffer (action &rest args)
 51    "Execute \"`docker-compose-command' ACTION ARGS\" and display output in a new buffer."
 52    (apply #'docker-run-async-with-buffer-interactive docker-compose-command (docker-compose-arguments) action args))
 53  
 54  (aio-defun docker-compose-services ()
 55    "Return the list of services."
 56    (s-split "\n" (aio-await (docker-compose-run-docker-compose-async "config" "--services" "2>/dev/null")) t))
 57  
 58  (aio-defun docker-compose-read-services-names ()
 59    "Read the services names."
 60    (completing-read-multiple "Services: " (aio-await (docker-compose-services))))
 61  
 62  (aio-defun docker-compose-read-service-name ()
 63    "Read one service name."
 64    (completing-read "Service: " (aio-await (docker-compose-services))))
 65  
 66  (defun docker-compose-read-project (prompt &rest _args)
 67    "Read the `docker-compose' project forwarding PROMPT."
 68    (completing-read
 69     prompt
 70     ;; in docker compose v2, we can obtain the list of
 71     ;; projects with 'ls' argument
 72     (if (string-match-p "\\bdocker\\s-+compose\\b" docker-compose-command)
 73         (split-string
 74  	(shell-command-to-string
 75  	 (concat docker-compose-command " ls" " --all" " -q"))
 76  	"\n"
 77  	t))))
 78  
 79  (defun docker-compose-read-log-level (prompt &rest _args)
 80    "Read the `docker-compose' log level forwarding PROMPT."
 81    (completing-read prompt '(DEBUG INFO WARNING ERROR CRITICAL)))
 82  
 83  (defun docker-compose-read-directory (prompt &optional initial-input _history)
 84    "Wrapper around `read-directory-name' forwarding PROMPT and INITIAL-INPUT."
 85    (read-directory-name prompt nil nil t initial-input))
 86  
 87  (defun docker-compose-read-environment-file (prompt &optional initial-input _history)
 88    "Wrapper around `read-file-name' forwarding PROMPT and INITIAL-INPUT."
 89    (read-file-name prompt nil nil t initial-input))
 90  
 91  (defun docker-compose-read-compose-file (prompt &optional initial-input _history)
 92    "Wrapper around `read-file-name' forwarding PROMPT and INITIAL-INPUT."
 93    (read-file-name prompt nil nil t initial-input (apply-partially 'string-match ".*\\.yml\\|.*\\.yaml")))
 94  
 95  (aio-defun docker-compose-run-action-for-one-service (action args services)
 96    "Run \"docker-compose ACTION ARGS SERVICES\"."
 97    (interactive (list
 98                  (-last-item (s-split "-" (symbol-name transient-current-command)))
 99                  (transient-args transient-current-command)
100                  nil))
101    (setq services (aio-await (docker-compose-read-services-names)))
102    (docker-compose-run-docker-compose-async-with-buffer action args services))
103  
104  (defun docker-compose-run-action-for-all-services (action args)
105    "Run \"docker-compose ACTION ARGS\"."
106    (interactive (list
107                  (-last-item (s-split "-" (symbol-name transient-current-command)))
108                  (transient-args transient-current-command)))
109    (docker-compose-run-docker-compose-async-with-buffer action args))
110  
111  (aio-defun docker-compose-run-action-with-command (action args service command)
112    "Run \"docker-compose ACTION ARGS SERVICE COMMAND\"."
113    (interactive (list
114                  (-last-item (s-split "-" (symbol-name transient-current-command)))
115                  (transient-args transient-current-command)
116                  nil
117                  (read-string "Command: ")))
118    (setq service (aio-await (docker-compose-read-service-name)))
119    (docker-compose-run-docker-compose-async-with-buffer action args service command))
120  
121  (transient-define-prefix docker-compose-build ()
122    "Transient for \"docker-compose build\"."
123    :man-page "docker-compose build"
124    ["Arguments"
125     ("b" "Build argument" "--build-arg " read-string)
126     ("c" "Compress build context" "--compress")
127     ("f" "Always remove intermediate containers" "--force-rm")
128     ("m" "Memory limit" "--memory " transient-read-number-N0)
129     ("n" "Do not use cache" "--no-cache")
130     ("p" "Attempt to pull a newer version of the image" "--pull")
131     ("r" "Build images in parallel" "--parallel")]
132    ["Actions"
133     ("B" "Build" docker-compose-run-action-for-one-service)
134     ("A" "All services" docker-compose-run-action-for-all-services)])
135  
136  (transient-define-prefix docker-compose-config ()
137    "Transient for \"docker-compose config\"."
138    :man-page "docker-compose config"
139    ["Arguments"
140  
141     ("r" "Pin image tags to digests" "--resolve-image-digests")
142     ("s" "Print the service names" "--services")
143     ("v" "Print the volume names" "--volumes")]
144    ["Actions"
145     ("V" "Config" docker-compose-run-action-for-all-services)])
146  
147  (transient-define-prefix docker-compose-create ()
148    "Transient for \"docker-compose create\"."
149    :man-page "docker-compose create"
150    ["Arguments"
151     ("b" "Build" "--build")
152     ("f" "Force recreate" "--force-recreate")
153     ("n" "No recreate" "--no-recreate")]
154    ["Actions"
155     ("C" "Create" docker-compose-run-action-for-one-service)
156     ("A" "All services" docker-compose-run-action-for-all-services)])
157  
158  (transient-define-prefix docker-compose-down ()
159    "Transient for \"docker-compose down\"."
160    :man-page "docker-compose down"
161    ["Arguments"
162     ("o" "Remove orphans" "--remove-orphans")
163     ("t" "Timeout" "--timeout " transient-read-number-N0)
164     ("v" "Remove volumes" "--volumes")]
165    ["Actions"
166     ("W" "Down" docker-compose-run-action-for-one-service)
167     ("A" "All services" docker-compose-run-action-for-all-services)])
168  
169  (transient-define-prefix docker-compose-exec ()
170    "Transient for \"docker-compose exec\"."
171    :man-page "docker-compose exec"
172    ["Arguments"
173     ("P" "Privileged" "--privileged")
174     ("T" "Disable pseudo-tty" "-T")
175     ("d" "Detach" "-d")
176     ("e" "Env KEY=VAL" "-e " read-string)
177     ("u" "User " "--user " read-string)
178     ("w" "Workdir" "--workdir " read-string)]
179    ["Actions"
180     ("E" "Exec" docker-compose-run-action-with-command)])
181  
182  (transient-define-prefix docker-compose-logs ()
183    "Transient for \"docker-compose logs\"."
184    :man-page "docker-compose logs"
185    ["Arguments"
186     ("T" "Tail" "--tail " read-string)
187     ("f" "Follow" "--follow")
188     ("n" "No color" "--no-color")
189     ("t" "Timestamps" "--timestamps")]
190    ["Actions"
191     ("L" "Logs" docker-compose-run-action-for-one-service)
192     ("A" "All services" docker-compose-run-action-for-all-services)])
193  
194  (transient-define-prefix docker-compose-pull ()
195    "Transient for \"docker-compose pull\"."
196    :man-page "docker-compose pull"
197    ["Arguments"
198     ("d" "Include dependencies" "--include-deps")
199     ("i" "Ignore pull failures" "--ignore-pull-failures")
200     ("n" "No parallel" "--no-parallel")]
201    ["Actions"
202     ("F" "Pull" docker-compose-run-action-for-one-service)
203     ("A" "All services" docker-compose-run-action-for-all-services)])
204  
205  (transient-define-prefix docker-compose-push ()
206    "Transient for \"docker-compose push\"."
207    :man-page "docker-compose push"
208    ["Arguments"
209     ("i" "Ignore push failures" "--ignore-push-failures")]
210    ["Actions"
211     ("P" "Push" docker-compose-run-action-for-one-service)
212     ("A" "All services" docker-compose-run-action-for-all-services)])
213  
214  (transient-define-prefix docker-compose-restart ()
215    "Transient for \"docker-compose restart\"."
216    :man-page "docker-compose restart"
217    ["Arguments"
218     ("t" "Timeout" "--timeout " transient-read-number-N0)]
219    ["Actions"
220     ("T" "Restart" docker-compose-run-action-for-one-service)
221     ("A" "All services" docker-compose-run-action-for-all-services)])
222  
223  (transient-define-prefix docker-compose-rm ()
224    "Transient for \"docker-compose rm\"."
225    :man-page "docker-compose rm"
226    ["Arguments"
227     ("f" "Force" "--force")
228     ("s" "Stop" "--stop")
229     ("v" "Remove anonymous volumes" "-v")]
230    ["Actions"
231     ("D" "Remove" docker-compose-run-action-for-one-service)
232     ("A" "All services" docker-compose-run-action-for-all-services)])
233  
234  (transient-define-prefix docker-compose-run ()
235    "Transient for \"docker-compose run\"."
236    :man-page "docker-compose run"
237    :value '("--rm")
238    ["Arguments"
239     ("E" "Entrypoint" "--entrypoint " read-string)
240     ("N" "Name" "--name " read-string)
241     ("T" "Disable pseudo-tty" "-T")
242     ("d" "Detach" "-d")
243     ("e" "Env KEY=VAL" "-e " read-string)
244     ("l" "Label" "--label " read-string)
245     ("n" "No deps" "--no-deps")
246     ("r" "Remove container when it exits" "--rm")
247     ("s" "Enable services ports" "--service-ports")
248     ("u" "User " "--user " read-string)
249     ("w" "Workdir" "--workdir " read-string)]
250    ["Actions"
251     ("R" "Run" docker-compose-run-action-with-command)])
252  
253  (transient-define-prefix docker-compose-start ()
254    "Transient for \"docker-compose start\"."
255    :man-page "docker-compose start"
256    ["Actions"
257     ("S" "Start" docker-compose-run-action-for-one-service)
258     ("A" "All services" docker-compose-run-action-for-all-services)])
259  
260  (transient-define-prefix docker-compose-stop ()
261    "Transient for \"docker-compose stop\"."
262    :man-page "docker-compose stop"
263    ["Arguments"
264     ("t" "Timeout" "--timeout " transient-read-number-N0)]
265    ["Actions"
266     ("O" "Stop" docker-compose-run-action-for-one-service)
267     ("A" "All services" docker-compose-run-action-for-all-services)])
268  
269  (transient-define-prefix docker-compose-up ()
270    "Transient for \"docker-compose up\"."
271    :man-page "docker-compose up"
272    ["Arguments"
273     ("b" "Build" "--build")
274     ("c" "Scale" "--scale " transient-read-number-N0)
275     ("d" "Detach" "-d")
276     ("f" "Force recreate" "--force-recreate")
277     ("n" "No deps" "--no-deps")
278     ("q" "Quiet pull" "--quiet-pull")
279     ("r" "Remove orphans" "--remove-orphans")
280     ("t" "Timeout" "--timeout " transient-read-number-N0)]
281    ["Actions"
282     ("U" "Up" docker-compose-run-action-for-one-service)
283     ("A" "All services" docker-compose-run-action-for-all-services)])
284  
285  (transient-define-prefix docker-compose-pause ()
286    "Transient for \"docker-compose pause\"."
287    :man-page "docker-compose pause"
288    ["Actions"
289     ("Z" "Pause" docker-compose-run-action-for-one-service)
290     ("A" "All services" docker-compose-run-action-for-all-services)])
291  
292  (transient-define-prefix docker-compose-unpause ()
293    "Transient for \"docker-compose unpause\"."
294    :man-page "docker-compose unpause"
295    ["Actions"
296     ("N" "Unpause" docker-compose-run-action-for-one-service)
297     ("A" "All services" docker-compose-run-action-for-all-services)])
298  
299  (docker-utils-define-transient-arguments docker-compose)
300  
301  ;;;###autoload (autoload 'docker-compose "docker-compose" nil t)
302  (transient-define-prefix docker-compose ()
303    "Transient for docker-compose."
304    :man-page "docker-compose"
305    ["Arguments"
306     ("a" "No ANSI" "--no-ansi")
307     ("c" "Compatibility" "--compatibility")
308     ("d" "Project directory" "--project-directory " docker-compose-read-directory)
309     ("e" "Environment file" "--env-file " docker-compose-read-environment-file)
310     ("f" "Compose file" "--file " docker-compose-read-compose-file)
311     ("h" "Host" "--host " read-string)
312     ("l" "Log level" "--log-level " docker-compose-read-log-level)
313     ("p" "Project name" "--project-name " docker-compose-read-project)
314     ("r" "Profile" "--profile " read-string)
315     ("v" "Verbose" "--verbose")]
316    [["Images"
317      ("B" "Build"      docker-compose-build)
318      ("F" "Pull"       docker-compose-pull)
319      ("P" "Push"       docker-compose-push)]
320     ["Containers"
321      ("C" "Create"     docker-compose-create)
322      ("D" "Remove"     docker-compose-rm)
323      ("Z" "Pause"      docker-compose-pause)
324      ("N" "Unpause"    docker-compose-unpause)
325      ("U" "Up"         docker-compose-up)
326      ("W" "Down"       docker-compose-down)]
327     ["State"
328      ("O" "Stop"       docker-compose-stop)
329      ("S" "Start"      docker-compose-start)
330      ("T" "Restart"    docker-compose-restart)]
331     ["Other"
332      ("E" "Exec"       docker-compose-exec)
333      ("L" "Logs"       docker-compose-logs)
334      ("R" "Run"        docker-compose-run)
335      ("V" "Config"     docker-compose-config)]])
336  
337  (provide 'docker-compose)
338  
339  ;;; docker-compose.el ends here