docker_helper 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,11 @@
1
+ # markup: rd
2
+
3
+ = Revision history for docker_helper
4
+
5
+ == 0.0.1 [2014-08-05]
6
+
7
+ * First release.
8
+
9
+ == 0.0.0 [2014-08-01]
10
+
11
+ * Birthday :-)
data/README ADDED
@@ -0,0 +1,64 @@
1
+ = docker_helper - Helper methods to interact with Docker
2
+
3
+ == VERSION
4
+
5
+ This documentation refers to docker_helper version 0.0.1.
6
+
7
+
8
+ == DESCRIPTION
9
+
10
+ Control the Docker[https://docker.com] command-line client from Ruby. Contrary
11
+ to {docker-api}[https://rubygems.org/gems/docker-api], this library only calls
12
+ the Docker client as an external process and thus doesn't need to run with
13
+ elevated privileges as a whole.
14
+
15
+ Basic usage:
16
+
17
+ # Initialize proxy object or include in your own class
18
+ docker = DockerHelper.proxy
19
+
20
+ class MyClass; include DockerHelper; end
21
+ docker = MyClass.new
22
+
23
+ # Call central helper with any Docker action and arguments
24
+ docker.docker('inspect', 'my-container')
25
+ docker.docker(:images).lines.drop(1)
26
+
27
+ # Call predefined helper methods (short form only for proxy object)
28
+ docker.docker_version
29
+ docker.version
30
+
31
+ docker.docker_build(path, name)
32
+ docker.build(path, name)
33
+
34
+ See DockerHelper for more information.
35
+
36
+
37
+ == LINKS
38
+
39
+ Documentation:: https://blackwinter.github.com/docker_helper
40
+ Source code:: https://github.com/blackwinter/docker_helper
41
+ RubyGem:: https://rubygems.org/gems/docker_helper
42
+
43
+
44
+ == AUTHORS
45
+
46
+ * Jens Wille <mailto:jens.wille@gmail.com>
47
+
48
+
49
+ == LICENSE AND COPYRIGHT
50
+
51
+ Copyright (C) 2014 Jens Wille
52
+
53
+ docker_helper is free software: you can redistribute it and/or modify it
54
+ under the terms of the GNU Affero General Public License as published by
55
+ the Free Software Foundation, either version 3 of the License, or (at your
56
+ option) any later version.
57
+
58
+ docker_helper is distributed in the hope that it will be useful, but
59
+ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
60
+ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
61
+ License for more details.
62
+
63
+ You should have received a copy of the GNU Affero General Public License
64
+ along with docker_helper. If not, see <http://www.gnu.org/licenses/>.
@@ -0,0 +1,23 @@
1
+ require File.expand_path(%q{../lib/docker_helper/version}, __FILE__)
2
+
3
+ begin
4
+ require 'hen'
5
+
6
+ Hen.lay! {{
7
+ gem: {
8
+ name: %q{docker_helper},
9
+ version: DockerHelper::VERSION,
10
+ summary: %q{Helper methods to interact with Docker.},
11
+ description: %q{Control the Docker command-line client from Ruby.},
12
+ author: %q{Jens Wille},
13
+ email: %q{jens.wille@gmail.com},
14
+ license: %q{AGPL-3.0},
15
+ homepage: :blackwinter,
16
+ dependencies: %w[],
17
+
18
+ required_ruby_version: '>= 1.9.3'
19
+ }
20
+ }}
21
+ rescue LoadError => err
22
+ warn "Please install the `hen' gem. (#{err})"
23
+ end
@@ -0,0 +1,361 @@
1
+ #--
2
+ ###############################################################################
3
+ # #
4
+ # docker_helper -- Helper methods to interact with Docker #
5
+ # #
6
+ # Copyright (C) 2014 Jens Wille #
7
+ # #
8
+ # Authors: #
9
+ # Jens Wille <jens.wille@gmail.com> #
10
+ # #
11
+ # docker_helper is free software; you can redistribute it and/or modify it #
12
+ # under the terms of the GNU Affero General Public License as published by #
13
+ # the Free Software Foundation; either version 3 of the License, or (at your #
14
+ # option) any later version. #
15
+ # #
16
+ # docker_helper is distributed in the hope that it will be useful, but #
17
+ # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY #
18
+ # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public #
19
+ # License for more details. #
20
+ # #
21
+ # You should have received a copy of the GNU Affero General Public License #
22
+ # along with docker_helper. If not, see <http://www.gnu.org/licenses/>. #
23
+ # #
24
+ ###############################################################################
25
+ #++
26
+
27
+ # Various helper methods to control the Docker command-line client. Main
28
+ # entrance point is the #docker helper. Use ::proxy for a self-contained
29
+ # controller object to call these methods on.
30
+ #
31
+ # See ::docker_command
32
+
33
+ module DockerHelper
34
+
35
+ class << self
36
+
37
+ # Setter for the Docker command (see ::docker_command).
38
+ attr_writer :docker_command
39
+
40
+ # call-seq:
41
+ # proxy -> aProxy
42
+ #
43
+ # Returns a new instance of Proxy with helper methods available both in
44
+ # abbreviated and unabbreviated form.
45
+ def proxy
46
+ Proxy.new.extend(self)
47
+ end
48
+
49
+ # call-seq:
50
+ # docker_command -> aString
51
+ #
52
+ # Returns the Docker command or aborts if none could be found.
53
+ #
54
+ # Override by setting the +DOCKER_COMMAND+ environment variable
55
+ # or by setting the +docker_command+ attribute on this module.
56
+ def docker_command
57
+ @docker_command ||= ENV.fetch('DOCKER_COMMAND') {
58
+ find_docker_command || abort('Docker command not found.') }
59
+ end
60
+
61
+ # :category: Internal
62
+ #
63
+ # Tries to find the Docker command for the host system. Usually,
64
+ # it's just +docker+, but on Debian-based systems it's +docker.io+.
65
+ def find_docker_command
66
+ commands = %w[docker docker.io]
67
+
68
+ require 'nuggets/file/which'
69
+ File.which_command(commands)
70
+ rescue LoadError
71
+ commands.first
72
+ end
73
+
74
+ # :category: Internal
75
+ #
76
+ # Extracts options hash from +args+ and applies default options.
77
+ #
78
+ # Returns the options hash as well as the values for +keys+, which
79
+ # will be removed from the options.
80
+ def extract_options(args, *keys)
81
+ options = args.last.is_a?(Hash) ? args.pop : {}
82
+
83
+ unless options.key?(:pipe)
84
+ options[:pipe] = args.first.is_a?(Symbol)
85
+ end
86
+
87
+ unless options.key?(:fail_if_empty)
88
+ options[:fail_if_empty] = options[:pipe]
89
+ end
90
+
91
+ [options, *keys.map(&options.method(:delete))]
92
+ end
93
+
94
+ # :category: Internal
95
+ #
96
+ # Builds arguments array suitable for #docker_system and #docker_pipe.
97
+ #
98
+ # Prefixes the command with +sudo+ unless the +NOSUDO+ environment
99
+ # variable is set.
100
+ #
101
+ # Flattens all arguments, converts them to strings and appends the
102
+ # options hash.
103
+ def build_args(args, options)
104
+ args.unshift(docker_command)
105
+ args.unshift(:sudo) unless ENV['NOSUDO']
106
+
107
+ args.flatten!
108
+ args.map!(&:to_s) << options
109
+ end
110
+
111
+ end
112
+
113
+ # call-seq:
114
+ # docker(cmd, args...)
115
+ # docker(cmd, args...) { ... }
116
+ #
117
+ # Central helper method to execute Docker commands.
118
+ #
119
+ # If the command (first argument) is a symbol or if the +pipe+ option
120
+ # is set, runs the command through #docker_pipe and returns the result
121
+ # as a string. Otherwise, runs the command through #docker_system and
122
+ # returns +true+ or +false+, indicating whether the command exited
123
+ # successfully or not.
124
+ #
125
+ # In the pipe case, calls #docker_fail when the +fail_if_empty+ option
126
+ # is set and the command returned no result.
127
+ #
128
+ # In the system case, ignores errors when the +ignore_errors+ option
129
+ # is set. The block is passed on to #docker_system if given.
130
+ def docker(*args, &block)
131
+ options, pipe, fail_if_empty, ignore_errors = DockerHelper.
132
+ extract_options(args, :pipe, :fail_if_empty, :ignore_errors)
133
+
134
+ DockerHelper.build_args(args, options)
135
+
136
+ if pipe
137
+ docker_pipe(*args).tap { |res|
138
+ if fail_if_empty && !res
139
+ docker_fail(*args)
140
+ end
141
+ }
142
+ else
143
+ if ignore_errors
144
+ options[:err] = :close
145
+ block ||= lambda { |*| }
146
+ end
147
+
148
+ docker_system(*args, &block)
149
+ end
150
+ end
151
+
152
+ # call-seq:
153
+ # docker_version -> aString
154
+ #
155
+ # Returns the version number of the Docker client.
156
+ #
157
+ # Command reference: {docker version
158
+ # }[https://docs.docker.com/reference/commandline/cli/#version]
159
+ def docker_version
160
+ docker(:version).lines.first.split.last
161
+ end
162
+
163
+ # call-seq:
164
+ # docker_tags(image) -> anArray
165
+ #
166
+ # Returns the tags associated with image +image+.
167
+ #
168
+ # Command reference: {docker images
169
+ # }[https://docs.docker.com/reference/commandline/cli/#images]
170
+ def docker_tags(image = nil)
171
+ image ||= docker_image_name
172
+
173
+ needle = image[/[^:]+/]
174
+
175
+ docker(:images).lines.map { |line|
176
+ repo, tag, = line.split
177
+ tag if repo == needle
178
+ }.compact.sort.sort_by { |tag|
179
+ tag.split('.').map(&:to_i)
180
+ }
181
+ end
182
+
183
+ # call-seq:
184
+ # docker_build(build_path, image)
185
+ #
186
+ # Builds the image +image+ from the Dockerfile and context at
187
+ # +build_path+.
188
+ #
189
+ # Command reference: {docker build
190
+ # }[https://docs.docker.com/reference/commandline/cli/#build]
191
+ def docker_build(build_path, image = nil)
192
+ image ||= docker_image_name
193
+
194
+ docker %W[build -t #{image} #{build_path}] # --force-rm
195
+ end
196
+
197
+ # call-seq:
198
+ # docker_volume(volume, name) -> aString
199
+ #
200
+ # Returns the path to volume +volume+ shared by container +name+.
201
+ #
202
+ # Command reference: {docker inspect
203
+ # }[https://docs.docker.com/reference/commandline/cli/#inspect]
204
+ def docker_volume(volume, name = nil)
205
+ name ||= docker_container_name
206
+
207
+ docker :inspect, %W[-f {{index\ .Volumes\ "#{volume}"}} #{name}]
208
+ end
209
+
210
+ # call-seq:
211
+ # docker_url(port, name) -> aString
212
+ #
213
+ # Returns the HTTP URL for container +name+ on port +port+. Fails
214
+ # if container is not running or the specified port is not exposed.
215
+ #
216
+ # Command reference: {docker port
217
+ # }[https://docs.docker.com/reference/commandline/cli/#port]
218
+ def docker_url(port, name = nil)
219
+ name ||= docker_container_name
220
+
221
+ "http://#{docker(:port, name, port)}"
222
+ end
223
+
224
+ # call-seq:
225
+ # docker_start(name, image)
226
+ #
227
+ # Starts container +name+ from image +image+. This will fail if a
228
+ # container with that name is already running. Use #docker_start!
229
+ # in case you want to unconditionally start the container.
230
+ #
231
+ # Runs the container detached and with all exposed ports published.
232
+ #
233
+ # Command reference: {docker run
234
+ # }[https://docs.docker.com/reference/commandline/cli/#run]
235
+ def docker_start(name = nil, image = nil)
236
+ name ||= docker_container_name
237
+ image ||= docker_image_name
238
+
239
+ docker %W[run -d -P --name #{name} #{image}]
240
+ end
241
+
242
+ # call-seq:
243
+ # docker_start!(name, image)
244
+ # docker_start!(name, image) { |name, image| ... }
245
+ #
246
+ # Unconditionally starts container +name+ from image +image+.
247
+ #
248
+ # If the container is already running, it's restarted (see #docker_restart).
249
+ # Otherwise, it's started (see #docker_start) and its +name+ and +image+ are
250
+ # yielded to the block if given.
251
+ def docker_start!(name = nil, image = nil)
252
+ name ||= docker_container_name
253
+ image ||= docker_image_name
254
+
255
+ docker_restart(name) || docker_start(name, image).tap {
256
+ yield name, image if block_given?
257
+ }
258
+ end
259
+
260
+ # call-seq:
261
+ # docker_stop(name)
262
+ #
263
+ # Stops container +name+.
264
+ #
265
+ # Command reference: {docker stop
266
+ # }[https://docs.docker.com/reference/commandline/cli/#stop]
267
+ def docker_stop(name = nil, _ = nil)
268
+ name ||= docker_container_name
269
+
270
+ docker %W[stop #{name}], ignore_errors: true
271
+ end
272
+
273
+ # call-seq:
274
+ # docker_restart(name)
275
+ #
276
+ # Restarts container +name+ by stopping (see #docker_stop) and then
277
+ # starting it.
278
+ #
279
+ # Command reference: {docker start
280
+ # }[https://docs.docker.com/reference/commandline/cli/#start]
281
+ def docker_restart(name = nil, _ = nil)
282
+ name ||= docker_container_name
283
+
284
+ docker_stop name
285
+ docker %W[start #{name}]
286
+ end
287
+
288
+ # call-seq:
289
+ # docker_clean(name)
290
+ #
291
+ # Stops and then removes container +name+, including associated volumes.
292
+ #
293
+ # Command reference: {docker rm
294
+ # }[https://docs.docker.com/reference/commandline/cli/#rm]
295
+ def docker_clean(name = nil, _ = nil)
296
+ name ||= docker_container_name
297
+
298
+ docker_stop name
299
+ docker %W[rm -v -f #{name}], ignore_errors: true
300
+ end
301
+
302
+ # call-seq:
303
+ # docker_clobber(name, image)
304
+ #
305
+ # Removes container +name+ (see #docker_clean) as well as image +image+.
306
+ #
307
+ # Command reference: {docker rmi
308
+ # }[https://docs.docker.com/reference/commandline/cli/#rmi]
309
+ def docker_clobber(name = nil, image = nil)
310
+ name ||= docker_container_name
311
+ image ||= docker_image_name
312
+
313
+ docker_clean name
314
+ docker %W[rmi #{image}], ignore_errors: true
315
+ end
316
+
317
+ # call-seq:
318
+ # docker_reset(name, image)
319
+ #
320
+ # Resets container +name+ by removing (see #docker_clean) and then
321
+ # starting (see #docker_start) it from image +image+.
322
+ def docker_reset(name = nil, image = nil)
323
+ docker_clean(name)
324
+ docker_start(name, image)
325
+ end
326
+
327
+ private
328
+
329
+ # Placeholder for default container name; must be implemented by
330
+ # utilizing class.
331
+ def docker_container_name
332
+ raise ArgumentError, 'container name missing', caller(1)
333
+ end
334
+
335
+ # Placeholder for default image name; must be implemented by
336
+ # utilizing class.
337
+ def docker_image_name
338
+ raise ArgumentError, 'image name missing', caller(1)
339
+ end
340
+
341
+ # Executes the command in a subprocess and returns its output as a
342
+ # string, or +nil+ if output is empty; override for different behaviour.
343
+ def docker_pipe(*args)
344
+ res = IO.popen(args, &:read).chomp
345
+ res unless res.empty?
346
+ end
347
+
348
+ # Executes the command in a subshell; override for different behaviour.
349
+ def docker_system(*args)
350
+ system(*args)
351
+ end
352
+
353
+ # Simply aborts; override for different behaviour.
354
+ def docker_fail(*args)
355
+ abort
356
+ end
357
+
358
+ end
359
+
360
+ require_relative 'docker_helper/version'
361
+ require_relative 'docker_helper/proxy'