docker_helper 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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'