docker-api 1.15.0 → 1.16.0

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.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +26 -1
  3. data/Rakefile +37 -0
  4. data/TESTING.md +61 -0
  5. data/lib/docker.rb +8 -1
  6. data/lib/docker/container.rb +44 -80
  7. data/lib/docker/exec.rb +98 -0
  8. data/lib/docker/image.rb +4 -3
  9. data/lib/docker/util.rb +79 -0
  10. data/lib/docker/version.rb +2 -2
  11. data/spec/docker/container_spec.rb +192 -56
  12. data/spec/docker/event_spec.rb +4 -4
  13. data/spec/docker/exec_spec.rb +197 -0
  14. data/spec/docker/image_spec.rb +131 -102
  15. data/spec/docker_spec.rb +37 -27
  16. data/spec/fixtures/build_from_dir/Dockerfile +2 -2
  17. data/spec/fixtures/export.tar +0 -0
  18. data/spec/fixtures/top/Dockerfile +2 -2
  19. data/spec/spec_helper.rb +10 -0
  20. data/spec/support/vcr.rb +5 -0
  21. data/spec/vcr/Docker/_authenticate_/with_valid_credentials/logs_in_and_sets_the_creds.yml +6 -8
  22. data/spec/vcr/Docker/_info/returns_the_info_as_a_Hash.yml +7 -9
  23. data/spec/vcr/Docker/_validate_version/when_nothing_is_raised/validate_version_/.yml +7 -9
  24. data/spec/vcr/Docker/_version/returns_the_version_as_a_Hash.yml +7 -9
  25. data/spec/vcr/Docker_Container/_all/when_the_HTTP_response_is_a_200/materializes_each_Container_into_a_Docker_Container.yml +46 -139
  26. data/spec/vcr/Docker_Container/_attach/with_normal_sized_chunks/yields_each_chunk.yml +62 -20
  27. data/spec/vcr/Docker_Container/_attach/with_very_small_chunks/yields_each_chunk.yml +62 -20
  28. data/spec/vcr/Docker_Container/_changes/returns_the_changes_as_an_array.yml +71 -28
  29. data/spec/vcr/Docker_Container/_commit/creates_a_new_Image_from_the_Container_s_changes.yml +69 -23
  30. data/spec/vcr/Docker_Container/_copy/when_the_file_does_not_exist/raises_an_error.yml +134 -0
  31. data/spec/vcr/Docker_Container/_copy/when_the_input_is_a_directory/yields_each_chunk_of_the_tarred_directory.yml +73 -290
  32. data/spec/vcr/Docker_Container/_copy/when_the_input_is_a_file/yields_each_chunk_of_the_tarred_file.yml +51 -211
  33. data/spec/vcr/Docker_Container/_create/when_creating_a_container_named_bob/should_have_name_set_to_bob.yml +36 -17
  34. data/spec/vcr/Docker_Container/_create/when_the_Container_does_not_yet_exist/when_the_HTTP_request_returns_a_200/sets_the_id.yml +30 -9
  35. data/spec/vcr/Docker_Container/_delete/deletes_the_container.yml +17 -23
  36. data/spec/vcr/Docker_Container/_exec/when_detach_is_true/returns_the_Docker_Exec_object.yml +151 -0
  37. data/spec/vcr/Docker_Container/_exec/when_passed_a_block/streams_the_stdout/stderr_messages.yml +153 -0
  38. data/spec/vcr/Docker_Container/_exec/when_passed_only_a_command/returns_the_stdout/stderr_messages.yml +153 -0
  39. data/spec/vcr/Docker_Container/_exec/when_stdin_object_is_passed/returns_the_stdout/stderr_messages.yml +100 -0
  40. data/spec/vcr/Docker_Container/_exec/when_tty_is_true/returns_the_raw_stdout/stderr_output.yml +152 -0
  41. data/spec/vcr/Docker_Container/_export/yields_each_chunk.yml +263 -28
  42. data/spec/vcr/Docker_Container/_get/when_the_HTTP_response_is_a_200/materializes_the_Container_into_a_Docker_Container.yml +36 -17
  43. data/spec/vcr/Docker_Container/_json/returns_the_description_as_a_Hash.yml +36 -17
  44. data/spec/vcr/Docker_Container/_kill/kills_the_container.yml +55 -53
  45. data/spec/vcr/Docker_Container/_kill/with_a_kill_signal/kills_the_container.yml +74 -96
  46. data/spec/vcr/Docker_Container/_logs/when_not_selecting_any_stream/raises_a_client_error.yml +84 -81
  47. data/spec/vcr/Docker_Container/_logs/when_selecting_stdout/returns_blank_logs.yml +34 -15
  48. data/spec/vcr/Docker_Container/_pause/pauses_the_container.yml +98 -45
  49. data/spec/vcr/Docker_Container/_restart/restarts_the_container.yml +88 -56
  50. data/spec/vcr/Docker_Container/_run/when_the_Container_s_command_does_not_return_status_code_of_0/raises_an_error.yml +39 -22
  51. data/spec/vcr/Docker_Container/_run/when_the_Container_s_command_returns_a_status_code_of_0/creates_a_new_container_to_run_the_specified_command.yml +212 -60
  52. data/spec/vcr/Docker_Container/_start/starts_the_container.yml +45 -30
  53. data/spec/vcr/Docker_Container/_stop/stops_the_container.yml +54 -83
  54. data/spec/vcr/Docker_Container/_streaming_logs/when_not_selecting_any_stream/raises_a_client_error.yml +84 -81
  55. data/spec/vcr/Docker_Container/_streaming_logs/when_selecting_stdout/returns_blank_logs.yml +46 -30
  56. data/spec/vcr/Docker_Container/_top/returns_the_top_commands_as_an_Array.yml +103 -40
  57. data/spec/vcr/Docker_Container/_unpause/unpauses_the_container.yml +81 -57
  58. data/spec/vcr/Docker_Container/_wait/waits_for_the_command_to_finish.yml +39 -22
  59. data/spec/vcr/Docker_Container/_wait/when_an_argument_is_given/sets_the_read_timeout_to_that_amount_of_time.yml +39 -22
  60. data/spec/vcr/Docker_Exec/_create/when_the_HTTP_request_returns_a_201/sets_the_id.yml +128 -0
  61. data/spec/vcr/Docker_Exec/_resize/when_exec_instance_has_TTY_enabled/returns_a_200.yml +155 -0
  62. data/spec/vcr/Docker_Exec/_start_/when_detach_is_set_to_false/block_is_passed/attaches_to_the_stream.yml +152 -0
  63. data/spec/vcr/Docker_Exec/_start_/when_detach_is_set_to_false/returns_the_stdout_and_stderr_messages.yml +152 -0
  64. data/spec/vcr/Docker_Exec/_start_/when_detach_is_set_to_true/returns_empty_stdout_and_stderr_messages.yml +151 -0
  65. data/spec/vcr/Docker_Exec/_start_/when_the_HTTP_request_returns_a_201/starts_the_exec_instance.yml +151 -0
  66. data/spec/vcr/Docker_Exec/_start_/when_the_command_has_already_run/raises_an_error.yml +151 -0
  67. data/spec/vcr/Docker_Image/_all/materializes_each_Image_into_a_Docker_Image.yml +86 -200
  68. data/spec/vcr/Docker_Image/_build/with_a_valid_Dockerfile/with_a_block_capturing_build_output/calls_the_block_and_passes_build_output.yml +10 -14
  69. data/spec/vcr/Docker_Image/_build/with_a_valid_Dockerfile/with_specifying_a_repo_in_the_query_parameters/builds_an_image_and_tags_it.yml +147 -39
  70. data/spec/vcr/Docker_Image/_build/with_a_valid_Dockerfile/without_query_parameters/builds_an_image.yml +10 -14
  71. data/spec/vcr/Docker_Image/_build/with_an_invalid_Dockerfile/throws_a_UnexpectedResponseError.yml +12 -14
  72. data/spec/vcr/Docker_Image/_build_from_dir/with_a_valid_Dockerfile/with_a_block_capturing_build_output/calls_the_block_and_passes_build_output.yml +118 -16
  73. data/spec/vcr/Docker_Image/_build_from_dir/with_a_valid_Dockerfile/with_credentials_passed/sends_X-Registry-Config_header.yml +118 -14
  74. data/spec/vcr/Docker_Image/_build_from_dir/with_a_valid_Dockerfile/with_no_query_parameters/builds_the_image.yml +109 -37
  75. data/spec/vcr/Docker_Image/_build_from_dir/with_a_valid_Dockerfile/with_specifying_a_repo_in_the_query_parameters/builds_the_image_and_tags_it.yml +214 -60
  76. data/spec/vcr/Docker_Image/_create/when_the_Image_does_not_yet_exist_and_the_body_is_a_Hash/sets_the_id_and_sends_Docker_creds.yml +41 -2631
  77. data/spec/vcr/Docker_Image/_exist_/when_the_image_does_exist/returns_true.yml +12 -19
  78. data/spec/vcr/Docker_Image/_get/when_the_image_does_exist/returns_the_new_image.yml +7 -9
  79. data/spec/vcr/Docker_Image/_history/returns_the_history_of_the_Image.yml +17 -198
  80. data/spec/vcr/Docker_Image/_import/when_the_argument_is_a_URI/when_the_URI_is_invalid/raises_an_error.yml +26 -30
  81. data/spec/vcr/Docker_Image/_import/when_the_argument_is_a_URI/when_the_URI_is_valid/returns_an_Image.yml +246 -22
  82. data/spec/vcr/Docker_Image/_import/when_the_file_does_exist/creates_the_Image.yml +35 -10
  83. data/spec/vcr/Docker_Image/_insert_local/when_a_direcory_is_passed/inserts_the_directory.yml +1249 -74
  84. data/spec/vcr/Docker_Image/_insert_local/when_removing_intermediate_containers/creates_a_new_image.yml +84 -94
  85. data/spec/vcr/Docker_Image/_insert_local/when_removing_intermediate_containers/leave_no_intermediate_containers.yml +70 -92
  86. data/spec/vcr/Docker_Image/_insert_local/when_the_local_file_does_exist/creates_a_new_Image_that_has_that_file.yml +125 -98
  87. data/spec/vcr/Docker_Image/_insert_local/when_the_local_file_does_not_exist/raises_an_error.yml +13 -60
  88. data/spec/vcr/Docker_Image/_insert_local/when_there_are_multiple_files_passed/creates_a_new_Image_that_has_each_file.yml +190 -135
  89. data/spec/vcr/Docker_Image/_json/returns_additional_information_about_image_image.yml +15 -198
  90. data/spec/vcr/Docker_Image/_push/pushes_the_Image.yml +169 -264
  91. data/spec/vcr/Docker_Image/_push/when_there_are_no_credentials/still_pushes.yml +175 -4543
  92. data/spec/vcr/Docker_Image/_refresh_/updates_the_info_hash.yml +90 -206
  93. data/spec/vcr/Docker_Image/_remove/when_no_name_is_given/removes_the_Image.yml +300 -0
  94. data/spec/vcr/Docker_Image/_run/when_the_argument_is_a_String/splits_the_String_by_spaces_and_creates_a_new_Container.yml +77 -207
  95. data/spec/vcr/Docker_Image/_run/when_the_argument_is_an_Array/creates_a_new_Container.yml +77 -207
  96. data/spec/vcr/Docker_Image/_run/when_the_argument_is_nil/command_configured_in_image/should_normally_show_result_if_image_has_Cmd_configured.yml +160 -0
  97. data/spec/vcr/Docker_Image/_run/when_the_argument_is_nil/no_command_configured_in_image/should_raise_an_error_if_no_command_is_specified.yml +16 -197
  98. data/spec/vcr/Docker_Image/_search/materializes_each_Image_into_a_Docker_Image.yml +151 -73
  99. data/spec/vcr/Docker_Image/_tag/tags_the_image_with_the_repo_name.yml +42 -196
  100. metadata +67 -41
  101. data/spec/vcr/Docker_Container/_commit/if_run_is_passed_it_saves_the_command_in_the_image/saves_the_command.yml +0 -58
  102. data/spec/vcr/Docker_Container/_streaming_logs/when_not_selecting_any_stream/returns_the_error_message.yml +0 -163
  103. data/spec/vcr/Docker_Container/_wait/when_an_argument_is_given/and_a_command_runs_for_too_long/raises_a_ServerError.yml +0 -58
  104. data/spec/vcr/Docker_Image/_build_from_dir/with_a_valid_Dockerfile/with_credentials_passed/sends_Docker_creds.yml +0 -41
  105. data/spec/vcr/Docker_Image/_remove/removes_the_Image.yml +0 -276
@@ -5,6 +5,85 @@ module Docker::Util
5
5
 
6
6
  module_function
7
7
 
8
+ # Attaches to a HTTP stream
9
+ #
10
+ # @param block
11
+ # @param msg_stack [Docker::Messages]
12
+ # @param tty [boolean]
13
+ def attach_for(block, msg_stack, tty = false)
14
+ # If TTY is enabled expect raw data and append to stdout
15
+ if tty
16
+ attach_for_tty(block, msg_stack)
17
+ else
18
+ attach_for_multiplex(block, msg_stack)
19
+ end
20
+ end
21
+
22
+ def attach_for_tty(block, msg_stack)
23
+ return lambda do |c,r,t|
24
+ msg_stack.stdout_messages << c
25
+ msg_stack.all_messages << c
26
+ block.call c if block
27
+ end
28
+ end
29
+
30
+ def attach_for_multiplex(block, msg_stack)
31
+ messages = Docker::Messages.new
32
+ lambda do |c,r,t|
33
+ messages = messages.decipher_messages(c)
34
+ msg_stack.append(messages)
35
+
36
+ unless block.nil?
37
+ messages.stdout_messages.each do |msg|
38
+ block.call(:stdout, msg)
39
+ end
40
+ messages.stderr_messages.each do |msg|
41
+ block.call(:stderr, msg)
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ def debug(msg)
48
+ Docker.logger.debug(msg) if Docker.logger
49
+ end
50
+
51
+ def hijack_for(stdin, block, msg_stack, tty)
52
+ attach_block = attach_for(block, msg_stack, tty)
53
+
54
+ lambda do |socket|
55
+ debug "hijack: hijacking the HTTP socket"
56
+ threads = []
57
+
58
+ debug "hijack: starting stdin copy thread"
59
+ threads << Thread.start do
60
+ debug "hijack: copying stdin => socket"
61
+ IO.copy_stream stdin, socket
62
+
63
+ debug "hijack: closing write end of hijacked socket"
64
+ socket.close_write
65
+ end
66
+
67
+ debug "hijack: starting hijacked socket read thread"
68
+ threads << Thread.start do
69
+ debug "hijack: reading from hijacked socket"
70
+
71
+ begin
72
+ while chunk = socket.readpartial(512)
73
+ debug "hijack: got #{chunk.bytesize} bytes from hijacked socket"
74
+ attach_block.call chunk, nil, nil
75
+ end
76
+ rescue EOFError
77
+ end
78
+
79
+ debug "hijack: killing stdin copy thread"
80
+ threads.first.kill
81
+ end
82
+
83
+ threads.each(&:join)
84
+ end
85
+ end
86
+
8
87
  def parse_json(body)
9
88
  JSON.parse(body) unless body.nil? || body.empty? || (body == 'null')
10
89
  rescue JSON::ParserError => ex
@@ -1,7 +1,7 @@
1
1
  module Docker
2
2
  # The version of the docker-api gem.
3
- VERSION = '1.15.0'
3
+ VERSION = '1.16.0'
4
4
 
5
5
  # The version of the compatible Docker remote API.
6
- API_VERSION = '1.12'
6
+ API_VERSION = '1.15'
7
7
  end
@@ -24,8 +24,11 @@ describe Docker::Container do
24
24
  end
25
25
 
26
26
  describe '#json' do
27
- subject { described_class.create('Cmd' => %w[true], 'Image' => 'base') }
27
+ subject {
28
+ described_class.create('Cmd' => %w[true], 'Image' => 'debian:wheezy')
29
+ }
28
30
  let(:description) { subject.json }
31
+ after(:each) { subject.remove }
29
32
 
30
33
  it 'returns the description as a Hash', :vcr do
31
34
  expect(description).to be_a Hash
@@ -34,7 +37,10 @@ describe Docker::Container do
34
37
  end
35
38
 
36
39
  describe '#streaming_logs' do
37
- subject { described_class.create('Cmd' => "echo hello", 'Image' => 'base') }
40
+ subject {
41
+ described_class.create('Cmd' => "echo hello", 'Image' => 'debian:wheezy')
42
+ }
43
+ after(:each) { subject.remove }
38
44
 
39
45
  context "when not selecting any stream" do
40
46
  let(:non_destination) { subject.logs }
@@ -53,7 +59,10 @@ describe Docker::Container do
53
59
  end
54
60
 
55
61
  describe '#logs' do
56
- subject { described_class.create('Cmd' => "echo hello", 'Image' => 'base') }
62
+ subject {
63
+ described_class.create('Cmd' => "echo hello", 'Image' => 'debian:wheezy')
64
+ }
65
+ after(:each) { subject.remove }
57
66
 
58
67
  context "when not selecting any stream" do
59
68
  let(:non_destination) { subject.logs }
@@ -73,11 +82,15 @@ describe Docker::Container do
73
82
 
74
83
  describe '#create' do
75
84
  subject {
76
- described_class.create({'Cmd' => %w[true], 'Image' => 'base'}.merge(opts))
85
+ described_class.create({
86
+ 'Cmd' => %w[true],
87
+ 'Image' => 'debian:wheezy'
88
+ }.merge(opts))
77
89
  }
78
90
 
79
91
  context 'when creating a container named bob' do
80
92
  let(:opts) { {"name" => "bob"} }
93
+ after(:each) { subject.remove }
81
94
 
82
95
  it 'should have name set to bob', :vcr do
83
96
  expect(subject.json["Name"]).to eq "/bob"
@@ -87,11 +100,15 @@ describe Docker::Container do
87
100
 
88
101
  describe '#changes' do
89
102
  subject {
90
- described_class.create('Cmd' => %w[rm -rf /root], 'Image' => 'base')
103
+ described_class.create(
104
+ 'Cmd' => %w[rm -rf /root],
105
+ 'Image' => 'debian:wheezy'
106
+ )
91
107
  }
92
108
  let(:changes) { subject.changes }
93
109
 
94
110
  before { subject.tap(&:start).tap(&:wait) }
111
+ after(:each) { subject.tap(&:wait).remove }
95
112
 
96
113
  it 'returns the changes as an array', :vcr do
97
114
  expect(changes).to eq [
@@ -110,26 +127,29 @@ describe Docker::Container do
110
127
  let(:image) { Docker::Image.build_from_dir(dir) }
111
128
  let(:top) { sleep 1; container.top }
112
129
  let!(:container) { image.run('/while') }
130
+ after do
131
+ container.kill!.remove
132
+ image.remove
133
+ end
113
134
 
114
135
  it 'returns the top commands as an Array', :vcr do
115
136
  expect(top).to be_a Array
116
137
  expect(top).to_not be_empty
117
- expect(top.first.keys).to eq %w(UID PID PPID C STIME TTY TIME CMD)
138
+ expect(top.first.keys).to include('PID')
118
139
  end
119
140
  end
120
141
 
121
142
  describe '#copy' do
122
- subject {
123
- Docker::Image.create(
124
- 'fromImage' => 'base'
125
- ).run('touch /test').tap { |c| c.wait }
126
- }
143
+ let(:image) { Docker::Image.create('fromImage' => 'debian:wheezy') }
144
+ subject { image.run('touch /test').tap { |c| c.wait } }
145
+
146
+ after(:each) { subject.remove }
127
147
 
128
148
  context 'when the file does not exist' do
129
149
  it 'raises an error', :vcr do
130
150
  skip 'Docker no longer returns a 500 when the file does not exist'
131
- expect { subject.copy('/lol/not/a/real/file') { |chunk| puts chunk } }
132
- .to raise_error
151
+ # expect { subject.copy('/lol/not/a/real/file') { |chunk| puts chunk } }
152
+ # .to raise_error
133
153
  end
134
154
  end
135
155
 
@@ -145,21 +165,19 @@ describe Docker::Container do
145
165
  context 'when the input is a directory' do
146
166
  it 'yields each chunk of the tarred directory', :vcr do
147
167
  chunks = []
148
- subject.copy('/etc/vim') { |chunk| chunks << chunk }
168
+ subject.copy('/etc/logrotate.d') { |chunk| chunks << chunk }
149
169
  chunks = chunks.join("\n")
150
- expect(%w[vimrc vimrc.tiny]).to be_all { |file| chunks.include?(file) }
170
+ expect(%w[apt dpkg]).to be_all { |file| chunks.include?(file) }
151
171
  end
152
172
  end
153
173
  end
154
174
 
155
175
  describe '#export' do
156
176
  subject { described_class.create('Cmd' => %w[rm -rf / --no-preserve-root],
157
- 'Image' => 'base') }
177
+ 'Image' => 'tianon/true') }
158
178
  before { subject.start }
179
+ after { subject.tap(&:wait).remove }
159
180
 
160
- # If you have to re-record this VCR, PLEASE edit it so that it's only ~200
161
- # lines. This is only because we don't want our gem to be a few hundred
162
- # megabytes.
163
181
  it 'yields each chunk', :vcr do
164
182
  first = nil
165
183
  subject.export do |chunk|
@@ -170,9 +188,15 @@ describe Docker::Container do
170
188
  end
171
189
 
172
190
  describe '#attach' do
173
- subject { described_class.create('Cmd' => %w[pwd], 'Image' => 'base') }
191
+ subject {
192
+ described_class.create(
193
+ 'Cmd' => ['bash','-c','sleep 2; echo hello'],
194
+ 'Image' => 'debian:wheezy'
195
+ )
196
+ }
174
197
 
175
198
  before { subject.start }
199
+ after(:each) { subject.stop.remove }
176
200
 
177
201
  context 'with normal sized chunks' do
178
202
  it 'yields each chunk', :vcr do
@@ -180,7 +204,7 @@ describe Docker::Container do
180
204
  subject.attach do |stream, c|
181
205
  chunk ||= c
182
206
  end
183
- expect(chunk).to eq("/\n")
207
+ expect(chunk).to eq("hello\n")
184
208
  end
185
209
  end
186
210
 
@@ -198,7 +222,7 @@ describe Docker::Container do
198
222
  subject.attach do |stream, c|
199
223
  chunk ||= c
200
224
  end
201
- expect(chunk).to eq("/\n")
225
+ expect(chunk).to eq("hello\n")
202
226
  end
203
227
  end
204
228
  end
@@ -211,7 +235,7 @@ describe Docker::Container do
211
235
  skip 'HTTP socket hijacking not compatible with VCR'
212
236
  container = described_class.create(
213
237
  'Cmd' => %w[cat],
214
- 'Image' => 'base',
238
+ 'Image' => 'debian:wheezy',
215
239
  'OpenStdin' => true,
216
240
  'StdinOnce' => true
217
241
  )
@@ -227,13 +251,14 @@ describe Docker::Container do
227
251
  subject {
228
252
  described_class.create(
229
253
  'Cmd' => %w[test -d /foo],
230
- 'Image' => 'base',
254
+ 'Image' => 'debian:wheezy',
231
255
  'Volumes' => {'/foo' => {}}
232
256
  )
233
257
  }
234
258
  let(:all) { Docker::Container.all }
235
259
 
236
260
  before { subject.start('Binds' => ["/tmp:/foo"]) }
261
+ after(:each) { subject.remove }
237
262
 
238
263
  it 'starts the container', :vcr do
239
264
  expect(all.map(&:id)).to be_any { |id| id.start_with?(subject.id) }
@@ -242,9 +267,12 @@ describe Docker::Container do
242
267
  end
243
268
 
244
269
  describe '#stop' do
245
- subject { described_class.create('Cmd' => %w[true], 'Image' => 'base') }
270
+ subject {
271
+ described_class.create('Cmd' => %w[true], 'Image' => 'debian:wheezy')
272
+ }
246
273
 
247
274
  before { subject.tap(&:start).stop('timeout' => '10') }
275
+ after { subject.remove }
248
276
 
249
277
  it 'stops the container', :vcr do
250
278
  expect(described_class.all(:all => true).map(&:id)).to be_any { |id|
@@ -256,13 +284,72 @@ describe Docker::Container do
256
284
  end
257
285
  end
258
286
 
287
+ describe '#exec' do
288
+ subject {
289
+ described_class.create(
290
+ 'Cmd' => %w[sleep 20],
291
+ 'Image' => 'debian:wheezy'
292
+ ).start
293
+ }
294
+ after { subject.kill!.remove }
295
+
296
+ context 'when passed only a command' do
297
+ let(:output) { subject.exec(['bash','-c','sleep 2; echo hello']) }
298
+
299
+ it 'returns the stdout/stderr messages', :vcr do
300
+ expect(output).to eq([["hello\n"], []])
301
+ end
302
+ end
303
+
304
+ context 'when detach is true' do
305
+ let(:output) { subject.exec('date', detach: true) }
306
+
307
+ it 'returns the Docker::Exec object', :vcr do
308
+ expect(output).to be_a Docker::Exec
309
+ expect(output.id).to_not be_nil
310
+ end
311
+ end
312
+
313
+ context 'when passed a block' do
314
+ it 'streams the stdout/stderr messages', :vcr do
315
+ chunk = nil
316
+ subject.exec(['bash','-c','sleep 2; echo hello']) do |stream, c|
317
+ chunk ||= c
318
+ end
319
+ expect(chunk).to eq("hello\n")
320
+ end
321
+ end
322
+
323
+ context 'when stdin object is passed' do
324
+ let(:output) { subject.exec('cat', stdin: StringIO.new("hello")) }
325
+
326
+ it 'returns the stdout/stderr messages', :vcr do
327
+ skip 'HTTP socket hijacking not compatible with VCR'
328
+ expect(output).to eq([["hello"],[]])
329
+ end
330
+ end
331
+
332
+ context 'when tty is true' do
333
+ let(:command) { [
334
+ "bash", "-c",
335
+ "if [ -t 1 ]; then echo -n \"I'm a TTY!\"; fi"
336
+ ] }
337
+ let(:output) { subject.exec(command, tty: true) }
338
+
339
+ it 'returns the raw stdout/stderr output', :vcr do
340
+ expect(output).to eq([["I'm a TTY!"], []])
341
+ end
342
+ end
343
+ end
344
+
259
345
  describe '#kill' do
260
346
  let(:command) { ['/bin/bash', '-c', 'while [ 1 ]; do echo hello; done'] }
261
- subject { described_class.create('Cmd' => command, 'Image' => 'base') }
347
+ subject {
348
+ described_class.create('Cmd' => command, 'Image' => 'debian:wheezy')
349
+ }
262
350
 
263
- before do
264
- subject.start
265
- end
351
+ before { subject.start }
352
+ after(:each) {subject.remove }
266
353
 
267
354
  it 'kills the container', :vcr do
268
355
  subject.kill
@@ -303,7 +390,9 @@ describe Docker::Container do
303
390
  end
304
391
 
305
392
  describe '#delete' do
306
- subject { described_class.create('Cmd' => ['ls'], 'Image' => 'base') }
393
+ subject {
394
+ described_class.create('Cmd' => ['ls'], 'Image' => 'debian:wheezy')
395
+ }
307
396
 
308
397
  it 'deletes the container', :vcr do
309
398
  subject.delete(:force => true)
@@ -314,9 +403,12 @@ describe Docker::Container do
314
403
  end
315
404
 
316
405
  describe '#restart' do
317
- subject { described_class.create('Cmd' => %w[sleep 50], 'Image' => 'base') }
406
+ subject {
407
+ described_class.create('Cmd' => %w[sleep 10], 'Image' => 'debian:wheezy')
408
+ }
318
409
 
319
410
  before { subject.start }
411
+ after { subject.kill!.remove }
320
412
 
321
413
  it 'restarts the container', :vcr do
322
414
  expect(described_class.all.map(&:id)).to be_any { |id|
@@ -335,8 +427,12 @@ describe Docker::Container do
335
427
 
336
428
  describe '#pause' do
337
429
  subject {
338
- described_class.create('Cmd' => %w[sleep 50], 'Image' => 'base').start
430
+ described_class.create(
431
+ 'Cmd' => %w[sleep 50],
432
+ 'Image' => 'debian:wheezy'
433
+ ).start
339
434
  }
435
+ after { subject.unpause.kill!.remove }
340
436
 
341
437
  it 'pauses the container', :vcr do
342
438
  subject.pause
@@ -346,9 +442,13 @@ describe Docker::Container do
346
442
 
347
443
  describe '#unpause' do
348
444
  subject {
349
- described_class.create('Cmd' => %w[sleep 50], 'Image' => 'base').start
445
+ described_class.create(
446
+ 'Cmd' => %w[sleep 50],
447
+ 'Image' => 'debian:wheezy'
448
+ ).start
350
449
  }
351
450
  before { subject.pause }
451
+ after { subject.kill!.remove }
352
452
 
353
453
  it 'unpauses the container', :vcr do
354
454
  subject.unpause
@@ -359,10 +459,15 @@ describe Docker::Container do
359
459
  end
360
460
 
361
461
  describe '#wait' do
362
- subject { described_class.create('Cmd' => %w[tar nonsense],
363
- 'Image' => 'base') }
462
+ subject {
463
+ described_class.create(
464
+ 'Cmd' => %w[tar nonsense],
465
+ 'Image' => 'debian:wheezy'
466
+ )
467
+ }
364
468
 
365
469
  before { subject.start }
470
+ after(:each) { subject.remove }
366
471
 
367
472
  it 'waits for the command to finish', :vcr do
368
473
  expect(subject.wait['StatusCode']).to_not be_zero
@@ -370,26 +475,33 @@ describe Docker::Container do
370
475
 
371
476
  context 'when an argument is given' do
372
477
  subject { described_class.create('Cmd' => %w[sleep 5],
373
- 'Image' => 'base') }
478
+ 'Image' => 'debian:wheezy') }
374
479
 
375
480
  it 'sets the :read_timeout to that amount of time', :vcr do
376
481
  expect(subject.wait(6)['StatusCode']).to be_zero
377
482
  end
378
483
 
379
- context 'and a command runs for too long' do
380
- it 'raises a ServerError', :vcr do
381
- skip "VCR doesn't like to record errors"
382
- expect { subject.wait(4) }.to raise_error(Docker::Error::TimeoutError)
383
- end
384
- end
484
+ # context 'and a command runs for too long' do
485
+ # after(:each) { subject.remove }
486
+ #
487
+ # it 'raises a ServerError', :vcr do
488
+ # skip "VCR doesn't like to record errors"
489
+ # expect{subject.wait(4)}.to raise_error(Docker::Error::TimeoutError)
490
+ # end
491
+ # end
385
492
  end
386
493
  end
387
494
 
388
495
  describe '#run' do
389
496
  let(:run_command) { subject.run('ls') }
497
+
390
498
  context 'when the Container\'s command does not return status code of 0' do
391
- subject { described_class.create('Cmd' => %w[lol not a real command],
392
- 'Image' => 'base') }
499
+ subject { described_class.create('Cmd' => %w[false],
500
+ 'Image' => 'debian:wheezy') }
501
+
502
+ after do
503
+ subject.remove
504
+ end
393
505
 
394
506
  it 'raises an error', :vcr do
395
507
  expect { run_command }
@@ -399,7 +511,16 @@ describe Docker::Container do
399
511
 
400
512
  context 'when the Container\'s command returns a status code of 0' do
401
513
  subject { described_class.create('Cmd' => %w[pwd],
402
- 'Image' => 'base') }
514
+ 'Image' => 'debian:wheezy') }
515
+ after do
516
+ subject.remove
517
+ image = run_command.json['Image']
518
+ run_command.remove
519
+ Docker::Image.get(image).history.each do |layer|
520
+ next unless layer['CreatedBy'] == 'pwd'
521
+ Docker::Image.get(layer['Id']).remove(:noprune => true)
522
+ end
523
+ end
403
524
 
404
525
  it 'creates a new container to run the specified command', :vcr do
405
526
  expect(run_command.wait['StatusCode']).to be_zero
@@ -408,23 +529,30 @@ describe Docker::Container do
408
529
  end
409
530
 
410
531
  describe '#commit' do
411
- subject { described_class.create('Cmd' => %w[true], 'Image' => 'base') }
532
+ subject {
533
+ described_class.create('Cmd' => %w[true], 'Image' => 'debian:wheezy')
534
+ }
412
535
  let(:image) { subject.commit }
413
536
 
414
537
  before { subject.start }
538
+ after(:each) do
539
+ subject.remove
540
+ image.remove
541
+ end
415
542
 
416
543
  it 'creates a new Image from the Container\'s changes', :vcr do
417
544
  expect(image).to be_a Docker::Image
418
545
  expect(image.id).to_not be_nil
419
546
  end
420
547
 
421
- context 'if run is passed, it saves the command in the image', :vcr do
422
- let(:image) { subject.commit }
423
- it 'saves the command' do
424
- skip 'This is no longer working in v0.8'
425
- expect(image.run('pwd').attach).to eql [["/\n"],[]]
426
- end
427
- end
548
+ # context 'if run is passed, it saves the command in the image', :vcr do
549
+ # let(:image) { subject.commit }
550
+ #
551
+ # it 'saves the command' do
552
+ # skip 'This is no longer working in v0.8'
553
+ # expect(image.run('pwd').attach).to eql [["/\n"],[]]
554
+ # end
555
+ # end
428
556
 
429
557
  end
430
558
 
@@ -464,12 +592,13 @@ describe Docker::Container do
464
592
  "Env" => nil,
465
593
  "Cmd" => ["date"],
466
594
  "Dns" => nil,
467
- "Image" => "base",
595
+ "Image" => "debian:wheezy",
468
596
  "Volumes" => {},
469
597
  "VolumesFrom" => ""
470
598
  }
471
599
  end
472
600
  let(:container) { subject.create(options) }
601
+ after { container.remove }
473
602
 
474
603
  it 'sets the id', :vcr do
475
604
  expect(container).to be_a Docker::Container
@@ -500,7 +629,10 @@ describe Docker::Container do
500
629
  end
501
630
 
502
631
  context 'when the HTTP response is a 200' do
503
- let(:container) { subject.create('Cmd' => ['ls'], 'Image' => 'base') }
632
+ let(:container) {
633
+ subject.create('Cmd' => ['ls'], 'Image' => 'debian:wheezy')
634
+ }
635
+ after { container.remove }
504
636
 
505
637
  it 'materializes the Container into a Docker::Container', :vcr do
506
638
  expect(subject.get(container.id)).to be_a Docker::Container
@@ -529,7 +661,11 @@ describe Docker::Container do
529
661
  end
530
662
 
531
663
  context 'when the HTTP response is a 200' do
532
- before { described_class.create('Cmd' => ['ls'], 'Image' => 'base') }
664
+ let(:container) {
665
+ subject.create('Cmd' => ['ls'], 'Image' => 'debian:wheezy')
666
+ }
667
+ before { container }
668
+ after { container.remove }
533
669
 
534
670
  it 'materializes each Container into a Docker::Container', :vcr do
535
671
  expect(subject.all(:all => true)).to be_all { |container|