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.
- checksums.yaml +7 -0
- data/COPYING +663 -0
- data/ChangeLog +11 -0
- data/README +64 -0
- data/Rakefile +23 -0
- data/lib/docker_helper.rb +361 -0
- data/lib/docker_helper/proxy.rb +57 -0
- data/lib/docker_helper/version.rb +27 -0
- data/spec/docker_helper_spec.rb +203 -0
- data/spec/spec_helper.rb +19 -0
- metadata +110 -0
data/ChangeLog
ADDED
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/>.
|
data/Rakefile
ADDED
@@ -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'
|