chef-metal-docker 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,9 @@
1
+ require 'chef_metal/machine/unix_machine'
2
+
3
+ module ChefMetalDocker
4
+ class DockerUnixMachine < ChefMetal::Machine::UnixMachine
5
+ def execute_always(command, options = {})
6
+ transport.execute(command, { :read_only => true }.merge(options))
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,146 @@
1
+ require 'chef/mixin/shell_out'
2
+ include Chef::Mixin::ShellOut
3
+
4
+ # Helpers module
5
+ module ChefMetalDocker
6
+ # Helpers::Docker module
7
+ module Helpers
8
+ # Exception to signify that the Docker daemon is not yet ready to handle
9
+ # docker commands.
10
+ class DockerNotReady < StandardError
11
+ def initialize(timeout)
12
+ super <<-EOH
13
+ The Docker daemon did not become ready within #{timeout} seconds.
14
+ This most likely means that Docker failed to start.
15
+ Docker can fail to start if:
16
+
17
+ - a configuration file is invalid
18
+ - permissions are incorrect for the root directory of the docker runtime.
19
+
20
+ If this problem persists, check your service log files.
21
+ EOH
22
+ end
23
+ end
24
+
25
+ # Exception to signify that the docker command timed out.
26
+ class CommandTimeout < RuntimeError; end
27
+
28
+ def cli_args(spec)
29
+ cli_line = ''
30
+ spec.each_pair do |arg, value|
31
+ case value
32
+ when Array
33
+ next if value.empty?
34
+ args = value.map do |v|
35
+ v = "\"#{v}\"" if v.is_a?(String)
36
+ " --#{arg}=#{v}"
37
+ end
38
+ cli_line += args.join
39
+ when FalseClass, Fixnum, Integer, String, TrueClass
40
+ value = "\"#{value}\"" if value.is_a?(String)
41
+ cli_line += " --#{arg}=#{value}"
42
+ end
43
+ end
44
+ cli_line
45
+ end
46
+
47
+ def docker_inspect(id)
48
+ require 'json'
49
+ JSON.parse(docker_cmd("inspect #{id}").stdout)[0]
50
+ end
51
+
52
+ def docker_inspect_id(id)
53
+ inspect = docker_inspect(id)
54
+ inspect['id'] if inspect
55
+ end
56
+
57
+ def dockercfg_parse
58
+ require 'json'
59
+ dockercfg = JSON.parse(::File.read(::File.join(::Dir.home, '.dockercfg')))
60
+ dockercfg.each_pair do |k, v|
61
+ dockercfg[k].merge!(dockercfg_parse_auth(v['auth']))
62
+ end
63
+ dockercfg
64
+ end
65
+
66
+ def dockercfg_parse_auth(str)
67
+ require 'base64'
68
+ decoded_str = Base64.decode64(str)
69
+ if decoded_str
70
+ auth = {}
71
+ auth['username'], auth['password'] = decoded_str.split(':')
72
+ auth
73
+ end
74
+ end
75
+
76
+ def timeout
77
+ 10
78
+ end
79
+
80
+ # This is based upon wait_until_ready! from the opscode jenkins cookbook.
81
+ #
82
+ # Since the docker service returns immediately and the actual docker
83
+ # process is started as a daemon, we block the Chef Client run until the
84
+ # daemon is actually ready.
85
+ #
86
+ # This method will effectively "block" the current thread until the docker
87
+ # daemon is ready
88
+ #
89
+ # @raise [DockerNotReady]
90
+ # if the Docker master does not respond within (+timeout+) seconds
91
+ #
92
+ def wait_until_ready!
93
+ Timeout.timeout(timeout) do
94
+ loop do
95
+ result = shell_out('docker info')
96
+ break if Array(result.valid_exit_codes).include?(result.exitstatus)
97
+ Chef::Log.debug("Docker daemon is not running - #{result.stdout}\n#{result.stderr}")
98
+ sleep(0.5)
99
+ end
100
+ end
101
+ rescue Timeout::Error
102
+ raise DockerNotReady.new(timeout), 'docker timeout exceeded'
103
+ end
104
+
105
+ # the Error message to display if a command times out. Subclasses
106
+ # may want to override this to provide more details on the timeout.
107
+ def command_timeout_error_message
108
+ <<-EOM
109
+
110
+ Command timed out:
111
+ #{cmd}
112
+
113
+ EOM
114
+ end
115
+
116
+ # Runs a docker command. Does not raise exception on non-zero exit code.
117
+ def docker_cmd(cmd, timeout = new_resource.cmd_timeout)
118
+ execute_cmd('docker ' + cmd, timeout)
119
+ end
120
+
121
+ # Executes the given command with the specified timeout. Does not raise an
122
+ # exception on a non-zero exit code.
123
+ def execute_cmd(cmd, timeout = new_resource.cmd_timeout)
124
+ Chef::Log.debug('Executing: ' + cmd)
125
+ begin
126
+ shell_out(cmd, :timeout => timeout)
127
+ rescue Mixlib::ShellOut::CommandTimeout
128
+ raise CommandTimeout, command_timeout_error_message(cmd)
129
+ end
130
+ end
131
+
132
+ # Executes the given docker command with the specified timeout. Raises an
133
+ # exception if the command returns a non-zero exit code.
134
+ def docker_cmd!(cmd, timeout = new_resource.cmd_timeout)
135
+ execute_cmd!('docker ' + cmd, timeout)
136
+ end
137
+
138
+ # Executes the given command with the specified timeout. Raises an
139
+ # exception if the command returns a non-zero exit code.
140
+ def execute_cmd!(cmd, timeout = new_resource.cmd_timeout)
141
+ cmd = execute_cmd(cmd, timeout)
142
+ cmd.error!
143
+ cmd
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,15 @@
1
+ require 'chef_metal_docker/helpers'
2
+ require 'chef_metal_docker/helpers/container/actions'
3
+ require 'chef_metal_docker/helpers/container/helpers'
4
+
5
+ module ChefMetalDocker
6
+ module Helpers
7
+ module Container
8
+
9
+ include ChefMetalDocker::Helpers
10
+ include ChefMetalDocker::Helpers::Container::Helpers
11
+ include ChefMetalDocker::Helpers::Container::Actions
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,313 @@
1
+ module ChefMetalDocker
2
+ module Helpers
3
+ module Container
4
+ # This is a collection of helper methods that are used to trigger actions in
5
+ # the LWRPs. By putting them in a separate file we can a) keep the LWRPs cleaner
6
+ # and b) unit test them!
7
+ module Actions
8
+
9
+ def commit
10
+ commit_args = cli_args(
11
+ 'author' => new_resource.author,
12
+ 'message' => new_resource.message,
13
+ 'run' => new_resource.run
14
+ )
15
+ commit_end_args = ''
16
+
17
+ if new_resource.repository
18
+ commit_end_args = new_resource.repository
19
+ commit_end_args += ":#{new_resource.tag}" if new_resource.tag
20
+ end
21
+
22
+ docker_cmd!("commit #{commit_args} #{current_resource.id} #{commit_end_args}")
23
+ end
24
+
25
+ def cp
26
+ docker_cmd!("cp #{current_resource.id}:#{new_resource.source} #{new_resource.destination}")
27
+ end
28
+
29
+ def export
30
+ docker_cmd!("export #{current_resource.id} > #{new_resource.destination}")
31
+ end
32
+
33
+ def kill
34
+ if service?
35
+ service_stop
36
+ else
37
+ docker_cmd!("kill #{current_resource.id}")
38
+ end
39
+ end
40
+
41
+ def remove
42
+ rm_args = cli_args(
43
+ 'force' => new_resource.force,
44
+ 'link' => new_resource.link
45
+ )
46
+ docker_cmd!("rm #{rm_args} #{current_resource.id}")
47
+ service_remove if service?
48
+ end
49
+
50
+ def redeploy
51
+ stop if running?
52
+ remove if exists?
53
+ run
54
+ end
55
+
56
+ def restart
57
+ if service?
58
+ service_restart
59
+ else
60
+ docker_cmd!("restart #{current_resource.id}")
61
+ end
62
+ end
63
+
64
+ # rubocop:disable MethodLength
65
+ def run
66
+ run_args = cli_args(
67
+ 'cpu-shares' => new_resource.cpu_shares,
68
+ 'cidfile' => cidfile,
69
+ 'detach' => new_resource.detach,
70
+ 'dns' => Array(new_resource.dns),
71
+ 'dns-search' => Array(new_resource.dns_search),
72
+ 'env' => Array(new_resource.env),
73
+ 'entrypoint' => new_resource.entrypoint,
74
+ 'expose' => Array(new_resource.expose),
75
+ 'hostname' => new_resource.hostname,
76
+ 'interactive' => new_resource.stdin,
77
+ 'label' => new_resource.label,
78
+ 'link' => Array(new_resource.link),
79
+ 'lxc-conf' => Array(new_resource.lxc_conf),
80
+ 'memory' => new_resource.memory,
81
+ 'networking' => new_resource.networking,
82
+ 'name' => container_name,
83
+ 'opt' => Array(new_resource.opt),
84
+ 'publish' => Array(port),
85
+ 'publish-all' => new_resource.publish_exposed_ports,
86
+ 'privileged' => new_resource.privileged,
87
+ 'rm' => new_resource.remove_automatically,
88
+ 'tty' => new_resource.tty,
89
+ 'user' => new_resource.user,
90
+ 'volume' => Array(new_resource.volume),
91
+ 'volumes-from' => new_resource.volumes_from,
92
+ 'workdir' => new_resource.working_directory
93
+ )
94
+ dr = docker_cmd!("run #{run_args} #{new_resource.image} #{new_resource.command}")
95
+ dr.error!
96
+ new_resource.id(dr.stdout.chomp)
97
+ service_create if service?
98
+ end
99
+ # rubocop:enable MethodLength
100
+
101
+ def service_action(actions)
102
+ if new_resource.init_type == 'runit'
103
+ runit_service service_name do
104
+ run_template_name 'docker-container'
105
+ action actions
106
+ end
107
+ else
108
+ service service_name do
109
+ case new_resource.init_type
110
+ when 'systemd'
111
+ provider Chef::Provider::Service::Systemd
112
+ when 'upstart'
113
+ provider Chef::Provider::Service::Upstart
114
+ end
115
+ supports :status => true, :restart => true, :reload => true
116
+ action actions
117
+ end
118
+ end
119
+ end
120
+
121
+ def service_create
122
+ case new_resource.init_type
123
+ when 'runit'
124
+ service_create_runit
125
+ when 'systemd'
126
+ service_create_systemd
127
+ when 'sysv'
128
+ service_create_sysv
129
+ when 'upstart'
130
+ service_create_upstart
131
+ end
132
+ end
133
+
134
+ def service_create_runit
135
+ runit_service service_name do
136
+ cookbook new_resource.cookbook
137
+ default_logger true
138
+ options(
139
+ 'service_name' => service_name
140
+ )
141
+ run_template_name service_template
142
+ end
143
+ end
144
+
145
+ def service_create_systemd
146
+ template "/usr/lib/systemd/system/#{service_name}.socket" do
147
+ if new_resource.socket_template.nil?
148
+ source 'docker-container.socket.erb'
149
+ else
150
+ source new_resource.socket_template
151
+ end
152
+ cookbook new_resource.cookbook
153
+ mode '0644'
154
+ owner 'root'
155
+ group 'root'
156
+ variables(
157
+ :service_name => service_name,
158
+ :sockets => sockets
159
+ )
160
+ not_if port.empty?
161
+ end
162
+
163
+ template "/usr/lib/systemd/system/#{service_name}.service" do
164
+ source service_template
165
+ cookbook new_resource.cookbook
166
+ mode '0644'
167
+ owner 'root'
168
+ group 'root'
169
+ variables(
170
+ :cmd_timeout => new_resource.cmd_timeout,
171
+ :service_name => service_name
172
+ )
173
+ end
174
+
175
+ service_action([:start, :enable])
176
+ end
177
+
178
+ def service_create_sysv
179
+ template "/etc/init.d/#{service_name}" do
180
+ source service_template
181
+ cookbook new_resource.cookbook
182
+ mode '0755'
183
+ owner 'root'
184
+ group 'root'
185
+ variables(
186
+ :cmd_timeout => new_resource.cmd_timeout,
187
+ :service_name => service_name
188
+ )
189
+ end
190
+
191
+ service_action([:start, :enable])
192
+ end
193
+
194
+ def service_create_upstart
195
+ # The upstart init script requires inotifywait, which is in inotify-tools
196
+ package 'inotify-tools'
197
+
198
+ template "/etc/init/#{service_name}.conf" do
199
+ source service_template
200
+ cookbook new_resource.cookbook
201
+ mode '0600'
202
+ owner 'root'
203
+ group 'root'
204
+ variables(
205
+ :cmd_timeout => new_resource.cmd_timeout,
206
+ :service_name => service_name
207
+ )
208
+ end
209
+
210
+ service_action([:start, :enable])
211
+ end
212
+
213
+ def service_remove
214
+ case new_resource.init_type
215
+ when 'runit'
216
+ service_remove_runit
217
+ when 'systemd'
218
+ service_remove_systemd
219
+ when 'sysv'
220
+ service_remove_sysv
221
+ when 'upstart'
222
+ service_remove_upstart
223
+ end
224
+ end
225
+
226
+ def service_remove_runit
227
+ runit_service service_name do
228
+ action :disable
229
+ end
230
+ end
231
+
232
+ def service_remove_systemd
233
+ service_action([:stop, :disable])
234
+
235
+ %w(service socket).each do |f|
236
+ file "/usr/lib/systemd/system/#{service_name}.#{f}" do
237
+ action :delete
238
+ end
239
+ end
240
+ end
241
+
242
+ def service_remove_sysv
243
+ service_action([:stop, :disable])
244
+
245
+ file "/etc/init.d/#{service_name}" do
246
+ action :delete
247
+ end
248
+ end
249
+
250
+ def service_remove_upstart
251
+ service_action([:stop, :disable])
252
+
253
+ file "/etc/init/#{service_name}" do
254
+ action :delete
255
+ end
256
+ end
257
+
258
+ def service_restart
259
+ service_action([:restart])
260
+ end
261
+
262
+ def service_start
263
+ service_action([:start])
264
+ end
265
+
266
+ def service_stop
267
+ service_action([:stop])
268
+ end
269
+
270
+ def service_template
271
+ return new_resource.init_template unless new_resource.init_template.nil?
272
+ case new_resource.init_type
273
+ when 'runit'
274
+ 'docker-container'
275
+ when 'systemd'
276
+ 'docker-container.service.erb'
277
+ when 'upstart'
278
+ 'docker-container.conf.erb'
279
+ when 'sysv'
280
+ 'docker-container.sysv.erb'
281
+ end
282
+ end
283
+
284
+ def start
285
+ start_args = cli_args(
286
+ 'attach' => new_resource.attach,
287
+ 'interactive' => new_resource.stdin
288
+ )
289
+ if service?
290
+ service_create
291
+ else
292
+ docker_cmd!("start #{start_args} #{current_resource.id}")
293
+ end
294
+ end
295
+
296
+ def stop
297
+ stop_args = cli_args(
298
+ 'time' => new_resource.cmd_timeout
299
+ )
300
+ if service?
301
+ service_stop
302
+ else
303
+ docker_cmd!("stop #{stop_args} #{current_resource.id}", (new_resource.cmd_timeout + 15))
304
+ end
305
+ end
306
+
307
+ def wait
308
+ docker_cmd!("wait #{current_resource.id}")
309
+ end
310
+ end
311
+ end
312
+ end
313
+ end