bigrig 0.0.0 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (146) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +20 -0
  5. data/.travis.yml +3 -0
  6. data/CHANGELOG.md +19 -0
  7. data/Gemfile +2 -0
  8. data/Gemfile.lock +84 -0
  9. data/LICENSE +28 -0
  10. data/README.md +191 -0
  11. data/Rakefile +10 -0
  12. data/bigrig.gemspec +29 -0
  13. data/bin/bigrig +113 -0
  14. data/lib/bigrig/actions/destroy_action.rb +21 -0
  15. data/lib/bigrig/actions/dev_action.rb +19 -0
  16. data/lib/bigrig/actions/log_action.rb +45 -0
  17. data/lib/bigrig/actions/run_action.rb +14 -0
  18. data/lib/bigrig/actions/ship_action.rb +51 -0
  19. data/lib/bigrig/actions.rb +5 -0
  20. data/lib/bigrig/dependency_graph.rb +21 -0
  21. data/lib/bigrig/descriptor.rb +43 -0
  22. data/lib/bigrig/docker_adapter.rb +129 -0
  23. data/lib/bigrig/models/application.rb +22 -0
  24. data/lib/bigrig/models/base_model.rb +7 -0
  25. data/lib/bigrig/models/container.rb +40 -0
  26. data/lib/bigrig/models.rb +3 -0
  27. data/lib/bigrig/output_parser.rb +52 -0
  28. data/lib/bigrig/runner.rb +94 -0
  29. data/lib/bigrig/version.rb +3 -0
  30. data/lib/bigrig.rb +8 -0
  31. data/spec/bigrig/actions/destroy_action_spec.rb +55 -0
  32. data/spec/bigrig/actions/dev_action_spec.rb +17 -0
  33. data/spec/bigrig/actions/log_action_spec.rb +33 -0
  34. data/spec/bigrig/actions/run_action_spec.rb +270 -0
  35. data/spec/bigrig/actions/ship_action_spec.rb +82 -0
  36. data/spec/bigrig/dependency_graph_spec.rb +19 -0
  37. data/spec/bigrig/descriptor_spec.rb +56 -0
  38. data/spec/bigrig/docker_adapter_spec.rb +409 -0
  39. data/spec/bigrig/models/application_spec.rb +32 -0
  40. data/spec/bigrig/models/container_spec.rb +115 -0
  41. data/spec/bigrig/output_parser_spec.rb +71 -0
  42. data/spec/bigrig_spec.rb +249 -0
  43. data/spec/data/addscontainer.json +24 -0
  44. data/spec/data/build/Dockerfile +2 -0
  45. data/spec/data/build/test +0 -0
  46. data/spec/data/dev.json +26 -0
  47. data/spec/data/duplicate.json +13 -0
  48. data/spec/data/env.json +12 -0
  49. data/spec/data/hosts_ip.json +12 -0
  50. data/spec/data/hosts_name.json +11 -0
  51. data/spec/data/links.json +13 -0
  52. data/spec/data/log.json +8 -0
  53. data/spec/data/multiple.json +13 -0
  54. data/spec/data/path.json +5 -0
  55. data/spec/data/ports.json +9 -0
  56. data/spec/data/profiles.json +24 -0
  57. data/spec/data/ship.json +5 -0
  58. data/spec/data/single.json +8 -0
  59. data/spec/data/tagandpath.json +24 -0
  60. data/spec/data/tiny-image.tar +0 -0
  61. data/spec/data/volumes.json +13 -0
  62. data/spec/spec_helper.rb +104 -0
  63. data/spec/support/bigrig_vcr +8 -0
  64. data/spec/support/vcr.rb +15 -0
  65. data/spec/vcr/Bigrig_DestroyAction/_perform/given_json_with_a_single_container/and_the_container_has_exited/should_remove_the_container.yml +392 -0
  66. data/spec/vcr/Bigrig_DestroyAction/_perform/given_json_with_a_single_container/and_the_container_is_running/kills_and_removes_the_container.yml +418 -0
  67. data/spec/vcr/Bigrig_DockerAdapter/_build/builds_the_given_directory.yml +63 -0
  68. data/spec/vcr/Bigrig_DockerAdapter/_build/passes_build_input_to_a_block.yml +35 -0
  69. data/spec/vcr/Bigrig_DockerAdapter/_container_exists_/when_the_container_does_not_exist/is_false.yml +115 -0
  70. data/spec/vcr/Bigrig_DockerAdapter/_container_exists_/when_the_container_exists/is_true.yml +82 -0
  71. data/spec/vcr/Bigrig_DockerAdapter/_image_id_by_tag/when_the_image_does_not_exist/raise_a_ImageNotFoundError.yml +172 -0
  72. data/spec/vcr/Bigrig_DockerAdapter/_image_id_by_tag/when_the_image_exists/returns_the_image_id.yml +148 -0
  73. data/spec/vcr/Bigrig_DockerAdapter/_kill/given_the_container_does_not_exist/should_raise_an_error.yml +115 -0
  74. data/spec/vcr/Bigrig_DockerAdapter/_kill/given_the_container_is_running/should_kill_the_container.yml +200 -0
  75. data/spec/vcr/Bigrig_DockerAdapter/_logs/streams_logs_to_a_block.yml +163 -0
  76. data/spec/vcr/Bigrig_DockerAdapter/_pull/given_a_block_to_capture_output/should_capture_output.yml +64 -0
  77. data/spec/vcr/Bigrig_DockerAdapter/_pull/given_the_repo_does_not_exist/raises_a_RepoNotFoundError.yml +30 -0
  78. data/spec/vcr/Bigrig_DockerAdapter/_pull/given_the_repo_exists/returns_the_image_id.yml +92 -0
  79. data/spec/vcr/Bigrig_DockerAdapter/_push/given_credentials/will_pass_login_and_password.yml +254 -0
  80. data/spec/vcr/Bigrig_DockerAdapter/_push/should_push_the_image.yml +482 -0
  81. data/spec/vcr/Bigrig_DockerAdapter/_remove_container/when_the_container_does_not_exist/raises_a_ContainerNotFoundError.yml +227 -0
  82. data/spec/vcr/Bigrig_DockerAdapter/_remove_container/when_the_container_exists/should_remove_the_container.yml +222 -0
  83. data/spec/vcr/Bigrig_DockerAdapter/_remove_container/when_the_container_is_running/raises_a_ContainerRunningError.yml +80 -0
  84. data/spec/vcr/Bigrig_DockerAdapter/_remove_image/when_the_image_doesnt_exist/raises_an_error.yml +115 -0
  85. data/spec/vcr/Bigrig_DockerAdapter/_remove_image/when_the_image_exists/removes_the_image.yml +199 -0
  86. data/spec/vcr/Bigrig_DockerAdapter/_run/given_an_image_id_that_exists/and_a_name/starts_the_container_with_the_right_name.yml +204 -0
  87. data/spec/vcr/Bigrig_DockerAdapter/_run/given_an_image_id_that_exists/and_a_name_and_env_variables/starts_the_container_with_env_set.yml +204 -0
  88. data/spec/vcr/Bigrig_DockerAdapter/_run/given_an_image_id_that_exists/and_a_name_and_ports/starts_the_container_with_ports_exposed.yml +230 -0
  89. data/spec/vcr/Bigrig_DockerAdapter/_running_/when_the_container_does_not_exist/returns_false.yml +115 -0
  90. data/spec/vcr/Bigrig_DockerAdapter/_running_/when_the_container_is_not_running/returns_false.yml +82 -0
  91. data/spec/vcr/Bigrig_DockerAdapter/_running_/when_the_container_is_running/returns_true.yml +103 -0
  92. data/spec/vcr/Bigrig_DockerAdapter/_tag/should_tag_the_image.yml +290 -0
  93. data/spec/vcr/Bigrig_LogAction/_perform/follows_the_log.yml +163 -0
  94. data/spec/vcr/Bigrig_RunAction/_perform/given_a_file_with_a_path/builds_the_image_before_starting_it.yml +310 -0
  95. data/spec/vcr/Bigrig_RunAction/_perform/given_a_file_with_hosts_by_ip/should_pass_hosts_to_container.yml +430 -0
  96. data/spec/vcr/Bigrig_RunAction/_perform/given_a_file_with_hosts_by_name/should_lookup_ips_for_hosts_with_a_hostname.yml +430 -0
  97. data/spec/vcr/Bigrig_RunAction/_perform/given_a_file_with_links/should_pass_links_to_the_right_container.yml +805 -0
  98. data/spec/vcr/Bigrig_RunAction/_perform/given_a_file_with_multiple_containers/launches_both_containers_in_parallel.yml +731 -0
  99. data/spec/vcr/Bigrig_RunAction/_perform/given_a_file_with_multiple_containers/spins_up_multiple_containers.yml +805 -0
  100. data/spec/vcr/Bigrig_RunAction/_perform/given_a_file_with_one_container/should_spin_up_a_single_container.yml +404 -0
  101. data/spec/vcr/Bigrig_RunAction/_perform/given_a_file_with_one_container/when_a_dead_container_exists/should_remove_existing_containers.yml +329 -0
  102. data/spec/vcr/Bigrig_RunAction/_perform/given_a_file_with_volumes_from/should_pass_volumes_from_to_the_right_container.yml +805 -0
  103. data/spec/vcr/Bigrig_RunAction/_perform/given_a_file_with_volumes_from/starts_the_dependant_container_last.yml +731 -0
  104. data/spec/vcr/Bigrig_RunAction/_perform/when_activating_profiles_that_do_not_exist/ignores_the_missing_profile.yml +409 -0
  105. data/spec/vcr/Bigrig_RunAction/_perform/with_a_file_with_active_profiles/uses_the_overridden_image.yml +432 -0
  106. data/spec/vcr/bigrig/destroy/spec/data/single_json/kills_the_container.yml +166 -0
  107. data/spec/vcr/bigrig/dev/spec/data/dev_json/activates_the_dev_profile.yml +37 -0
  108. data/spec/vcr/bigrig/dev/spec/data/dev_json/destroys_containers_on_exit.yml +227 -0
  109. data/spec/vcr/bigrig/dev/spec/data/dev_json/starts_the_containers.yml +107 -0
  110. data/spec/vcr/bigrig/logs/spec/data/log_json/tails_the_logs.yml +163 -0
  111. data/spec/vcr/bigrig/run/spec/data/profiles_json_-p_qa/leaves_existing_env_values_alone.yml +110 -0
  112. data/spec/vcr/bigrig/run/spec/data/profiles_json_-p_qa/overrides_the_env.yml +110 -0
  113. data/spec/vcr/bigrig/run/spec/data/profiles_json_-p_qa/overrides_the_tag.yml +103 -0
  114. data/spec/vcr/bigrig/run/spec/data/single_json/sends_the_name_of_the_container_to_stdout.yml +75 -0
  115. data/spec/vcr/bigrig/run/spec/data/single_json/starts_the_container.yml +101 -0
  116. data/spec/vcr/bigrig/ship/spec/data/ship_json/with_a_version/-c/cleans_the_image_when_its_done.yml +212 -0
  117. data/spec/vcr/bigrig/ship/spec/data/ship_json/with_a_version/builds_and_pushes_the_image.yml +128 -0
  118. data/spec/vcr/bigrig_bin_bigrig_destroy_spec/data/single_json_kills_the_container.yml +179 -0
  119. data/spec/vcr/bigrig_bin_bigrig_dev_spec/data/dev_json_activates_the_dev_profile.yml +1068 -0
  120. data/spec/vcr/bigrig_bin_bigrig_dev_spec/data/dev_json_destroys_containers_on_exit.yml +1070 -0
  121. data/spec/vcr/bigrig_bin_bigrig_dev_spec/data/dev_json_starts_the_containers.yml +1068 -0
  122. data/spec/vcr/bigrig_bin_bigrig_dev_spec/data/dev_json_tails_the_logs.yml +1075 -0
  123. data/spec/vcr/bigrig_bin_bigrig_logs_spec/data/log_json_tails_the_logs.yml +69 -0
  124. data/spec/vcr/bigrig_bin_bigrig_run_spec/data/profiles_json_-p_qa_leaves_existing_env_values_alone.yml +306 -0
  125. data/spec/vcr/bigrig_bin_bigrig_run_spec/data/profiles_json_-p_qa_overrides_the_env.yml +306 -0
  126. data/spec/vcr/bigrig_bin_bigrig_run_spec/data/profiles_json_-p_qa_overrides_the_tag.yml +306 -0
  127. data/spec/vcr/bigrig_bin_bigrig_run_spec/data/single_json_sends_the_name_of_the_container_to_stdout.yml +306 -0
  128. data/spec/vcr/bigrig_bin_bigrig_run_spec/data/single_json_starts_the_container.yml +306 -0
  129. data/spec/vcr/bigrig_bin_bigrig_ship_spec/data/ship_json_with_a_version_-c_cleans_the_image_when_its_done.yml +335 -0
  130. data/spec/vcr/bigrig_bin_bigrig_ship_spec/data/ship_json_with_a_version_builds_and_pushes_the_image.yml +285 -0
  131. data/test/dev/shipper.json +24 -0
  132. data/test/logs/bigrig.json +6 -0
  133. data/test/logs/container1/Dockerfile +4 -0
  134. data/test/logs/container1/run.sh +7 -0
  135. data/test/logs/container2/Dockerfile +4 -0
  136. data/test/logs/container2/run.sh +7 -0
  137. data/test/ship/bigrig-1.2.3.json +1 -0
  138. data/test/ship/bigrig.json +5 -0
  139. data/test/ship/registry.json +11 -0
  140. data/test/ship/ship/Dockerfile +4 -0
  141. data/test/ship/ship/run.sh +6 -0
  142. data/test/volumes_from/exports_volumes/Dockerfile +8 -0
  143. data/test/volumes_from/exports_volumes/index.html +8 -0
  144. data/test/volumes_from/exports_volumes/run.sh +7 -0
  145. data/test/volumes_from/shipper.json +14 -0
  146. metadata +278 -8
@@ -0,0 +1,409 @@
1
+ require 'pry'
2
+ module Bigrig
3
+ describe DockerAdapter do
4
+
5
+ describe '::build' do
6
+ subject { described_class.build path }
7
+ let(:path) { test_file 'build' }
8
+ let(:image_id) { subject }
9
+
10
+ it 'builds the given directory', :vcr do
11
+ expect { Docker::Image.get(image_id) }.to_not raise_error
12
+ end
13
+
14
+ it 'passes build input to a block', :vcr do
15
+ described_class.build path do |chunk|
16
+ puts "chunk: #{chunk}"
17
+ end
18
+ end
19
+ end
20
+
21
+ describe '::container_exists?' do
22
+ subject { described_class.container_exists?(name) }
23
+
24
+ context 'when the container exists' do
25
+ let!(:container) { Docker::Container.create 'name' => name, 'Image' => 'hawknewton/true' }
26
+ let(:name) { 'container_exists' }
27
+
28
+ after { container.remove }
29
+
30
+ it 'is true', :vcr do
31
+ is_expected.to be true
32
+ end
33
+ end
34
+
35
+ context 'when the container does not exist' do
36
+ let(:name) { 'container_does_not_exist' }
37
+
38
+ it 'is false', :vcr do
39
+ is_expected.to be false
40
+ end
41
+ end
42
+ end
43
+
44
+ describe '::image_id_by_tag' do
45
+ subject { DockerAdapter.image_id_by_tag(tag) }
46
+
47
+ context 'when the image exists' do
48
+ let(:tag) { 'image_exists' }
49
+ let!(:image) { Docker::Image.create 'fromImage' => 'hawknewton/true' }
50
+
51
+ # Get the long id
52
+ let(:image_id) { Docker::Image.get(image.id).id }
53
+
54
+ before { image.tag 'repo' => tag }
55
+ after { image.remove name: tag }
56
+
57
+ it 'returns the image id', :vcr do
58
+ is_expected.to eq image_id
59
+ end
60
+ end
61
+
62
+ context 'when the image does not exist' do
63
+ let(:tag) { 'hawknewton/true:not_exist' }
64
+
65
+ before do
66
+ begin
67
+ image = Docker::Image.get tag
68
+ image.remove name: tag
69
+ rescue Docker::Error::NotFoundError # rubocop:disable Lint/HandleExceptions
70
+ end
71
+ end
72
+
73
+ it 'raise a ImageNotFoundError', :vcr do
74
+ expect { subject }.to raise_error ImageNotFoundError
75
+ end
76
+ end
77
+ end
78
+
79
+ describe '::kill' do
80
+ subject { described_class.kill container_id }
81
+
82
+ context 'given the container is running' do
83
+ let(:container) { Docker::Container.create('Image' => image.id).tap(&:start) }
84
+ let(:container_id) { container.id }
85
+ let(:image) { Docker::Image.create 'fromImage' => 'hawknewton/show-env' }
86
+ let(:running?) { container.json['State']['Running'] }
87
+ let!(:kill) { subject }
88
+
89
+ after { container.delete force: true }
90
+
91
+ it 'should kill the container', :vcr do
92
+ expect(running?).to be false
93
+ end
94
+ end
95
+
96
+ context 'given the container does not exist' do
97
+ let(:container_id) { 'doesnotexist' }
98
+
99
+ it 'should raise an error', :vcr do
100
+ expect { subject }.to raise_error ContainerNotFoundError
101
+ end
102
+ end
103
+ end
104
+
105
+ describe '::logs' do
106
+ subject { described_class.logs(container.id) { |s, c| logs << [s, c] } }
107
+ let(:logs) { [] }
108
+ let(:image) { Docker::Image.create 'fromImage' => 'hawknewton/log-test:0.0.1' }
109
+ let(:container) { Docker::Container.get 'log-test' }
110
+
111
+ before do
112
+ Docker::Container.create('Image' => image.id, 'name' => 'log-test').start
113
+ end
114
+
115
+ after { container.kill.delete }
116
+
117
+ it 'streams logs to a block', :vcr do
118
+ allow(Docker::Container).to receive(:get).with(container.id, {}, anything).
119
+ and_return container
120
+ expect(container).to receive(:streaming_logs).
121
+ with(follow: true, stdout: true, stderr: true).
122
+ and_yield('stream #1', 'I am log message #1').
123
+ and_yield('stream #2', 'I am log message #2')
124
+
125
+ subject
126
+ expect(logs).to include ['stream #1', 'I am log message #1']
127
+ expect(logs).to include ['stream #2', 'I am log message #2']
128
+ end
129
+ end
130
+
131
+ describe '::push' do
132
+ subject { described_class.push tag, credentials }
133
+ let(:credentials) { nil }
134
+ let(:host) { URI.parse(Docker.connection.url).host }
135
+ let(:image) { Docker::Image.import test_file 'tiny-image.tar' }
136
+ let(:repo) { "#{host}:5000/test/push-me" }
137
+ let(:version) { '1.2.3' }
138
+ let(:tag) { "#{repo}:#{version}" }
139
+ let(:registry) do
140
+ Docker::Image.create 'fromImage' => 'registry:latest'
141
+ Docker::Container.create(
142
+ 'name' => 'registry',
143
+ 'Image' => 'registry',
144
+ 'Env' => ['GUNICORN_OPTS=[--preload]'],
145
+ 'ExposedPorts' => {
146
+ '5000/tcp' => {}
147
+ },
148
+ 'HostConfig' => {
149
+ 'PortBindings' => { '5000/tcp' => [{ 'HostPort' => '5000' }] }
150
+ }
151
+ )
152
+ end
153
+
154
+ before do
155
+ registry.start
156
+ image.tag 'repo' => repo, 'tag' => version
157
+ sleep 1
158
+ end
159
+
160
+ after do
161
+ registry.kill.delete
162
+ image.remove 'force' => true
163
+ end
164
+
165
+ it 'should push the image', :vcr do
166
+ subject
167
+ Docker::Image.get(tag).remove 'force' => true
168
+ expect { Docker::Image.create 'fromImage' => tag }.to_not raise_error
169
+ end
170
+
171
+ context 'given credentials' do
172
+ let(:credentials) do
173
+ {
174
+ 'username' => 'username',
175
+ 'password' => 'pass',
176
+ 'email' => 'email@email.com',
177
+ 'serveraddress' => 'https://index.docker.io/v1'
178
+ }
179
+ end
180
+ # I don't have the energy to set one of these up for real
181
+
182
+ it 'will pass login and password', :vcr do
183
+ creds = {
184
+ 'username' => 'username',
185
+ 'password' => 'pass',
186
+ 'serveraddress' => 'https://index.docker.io/v1',
187
+ 'email' => 'email@email.com'
188
+ }
189
+ expect_any_instance_of(Docker::Image).to receive(:push).
190
+ with(creds, {})
191
+ subject
192
+ end
193
+ end
194
+ end
195
+
196
+ describe '::pull' do
197
+ subject { described_class.pull repo }
198
+
199
+ context 'given the repo exists' do
200
+ let(:repo) { 'hawknewton/true:latest' }
201
+ let(:image_id) { Docker::Image.get(repo).id }
202
+
203
+ it 'returns the image id', :vcr do
204
+ is_expected.to eq image_id
205
+ end
206
+ end
207
+
208
+ context 'given the repo does not exist' do
209
+ let(:repo) { 'hawknewton/doesnotexist' }
210
+
211
+ it 'raises a RepoNotFoundError', :vcr do
212
+ expect { subject }.to raise_error RepoNotFoundError
213
+ end
214
+ end
215
+
216
+ context 'given a block to capture output' do
217
+ let(:repo) { 'hawknewton/true' }
218
+ let(:output) { '' }
219
+ let(:block) { proc { |chunk| output << chunk } }
220
+ let!(:image_id) { described_class.pull repo, &block }
221
+
222
+ it 'should capture output', :vcr do
223
+ expect(output).to match(/Pulling repository hawknewton\/true/)
224
+ end
225
+ end
226
+ end
227
+
228
+ describe '::remove_container' do
229
+ subject { described_class.remove_container container_id }
230
+
231
+ context 'when the container exists' do
232
+ let(:container_id) { Docker::Container.create('Image' => 'hawknewton/true').id }
233
+ let!(:remove) { subject }
234
+
235
+ it 'should remove the container', :vcr do
236
+ expect { Docker::Container.get container_id }.
237
+ to raise_error { Docker::Error::NotFoundError }
238
+ end
239
+ end
240
+
241
+ context 'when the container does not exist' do
242
+ let(:container_id) { 'doesnotexist' }
243
+
244
+ it 'raises a ContainerNotFoundError', :vcr do
245
+ expect { subject }.to raise_error ContainerNotFoundError
246
+ end
247
+ end
248
+
249
+ context 'when the container is running' do
250
+ let(:container_id) do
251
+ Docker::Container.create('Image' => 'hawknewton/true').tap(&:start).id
252
+ end
253
+
254
+ it 'raises a ContainerRunningError', :vcr do
255
+ expect { subject }.to raise_error ContainerRunningError
256
+ end
257
+ end
258
+ end
259
+
260
+ describe '::remove_image' do
261
+ subject { described_class.remove_image image_id }
262
+
263
+ context 'when the image exists' do
264
+ let(:image_id) { Docker::Image.import(test_file 'tiny-image.tar').id }
265
+
266
+ it 'removes the image', :vcr do
267
+ subject
268
+ expect { Docker::Image.get(image_id) }.to raise_error(/404 Not Found/)
269
+ end
270
+ end
271
+
272
+ context 'when the image doesn''t exist' do
273
+ let(:image_id) { 'does_not_exist' }
274
+
275
+ it 'raises an error', :vcr do
276
+ expect { subject }.to raise_error ImageNotFoundError
277
+ end
278
+ end
279
+ end
280
+
281
+ describe '::run' do
282
+ subject { DockerAdapter.run({ image_id: image_id }.merge opts) }
283
+
284
+ context 'given an image_id that exists' do
285
+ let(:image_id) { 'hawknewton/show-env' }
286
+ let(:container_id) { subject }
287
+ let(:container) { Docker::Container.get container_id }
288
+ let(:running?) { container.json['State']['Running'] }
289
+
290
+ context 'and a name' do
291
+ let(:opts) { { name: name } }
292
+ let(:name) { 'and_a_name' }
293
+
294
+ # strip the leading '/' from the json name
295
+ let(:container_name) { container.json['Name'][1..-1] }
296
+
297
+ after { Docker::Container.get(name).kill!.delete }
298
+
299
+ it 'starts the container with the right name', :vcr do
300
+ expect(running?).to be true
301
+ expect(container_name).to eq name
302
+ end
303
+ end
304
+
305
+ context 'and a name and ports' do
306
+ let(:free_port) { TCPServer.new('0.0.0.0', 0).addr[1] }
307
+ let(:opts) { { name: name, ports: ["#{free_port}:80", '70'] } }
308
+ let(:name) { 'with_ports' }
309
+ let(:exposed_ports) { container.json['Config']['ExposedPorts'].map { |k, _| k } }
310
+ let(:mapped_ports) do
311
+ container.json['NetworkSettings']['Ports'].each_with_object({}) do |arr, hash|
312
+ hash[arr[0]] = arr[1][0]['HostPort']
313
+ end
314
+ end
315
+
316
+ after { Docker::Container.get(name).kill!.delete }
317
+
318
+ it 'starts the container with ports exposed', :vcr do
319
+ expect(running?).to be true
320
+ expect(exposed_ports).to eq ['70/tcp', '80/tcp']
321
+ expect(mapped_ports).to include '80/tcp'
322
+ expect(mapped_ports).to include '70/tcp'
323
+
324
+ # Because the free port will move around under VCR we can't actually
325
+ # assert anything
326
+ expect(mapped_ports['80/tcp'].to_i).to be_a Fixnum
327
+ expect(mapped_ports['70/tcp'].to_i).to be_a Fixnum
328
+ end
329
+ end
330
+
331
+ context 'and a name and env variables' do
332
+ let(:name) { 'with_env' }
333
+ let(:opts) { { name: name, env: { 'NAME1' => 'VALUE1', 'NAME2' => 'VALUE2' } } }
334
+ let(:envs) { container.json['Config']['Env'] }
335
+
336
+ after { Docker::Container.get(name).kill!.delete }
337
+
338
+ it 'starts the container with env set', :vcr do
339
+ expect(running?).to be true
340
+ expect(envs).to include 'NAME1=VALUE1'
341
+ expect(envs).to include 'NAME2=VALUE2'
342
+ end
343
+ end
344
+ end
345
+ end
346
+
347
+ describe '::running?' do
348
+ subject { described_class.running? container_id }
349
+ let(:result) { subject }
350
+
351
+ context 'when the container is running' do
352
+ let(:container) do
353
+ Docker::Container.create('Image' => 'hawknewton/show-env')
354
+ end
355
+ let(:container_id) { container.id }
356
+
357
+ before { container.start }
358
+ after { container.delete force: true }
359
+
360
+ it 'returns true', :vcr do
361
+ expect(result).to be true
362
+ end
363
+ end
364
+
365
+ context 'when the container is not running' do
366
+ let(:container) do
367
+ Docker::Container.create('Image' => 'hawknewton/show-env')
368
+ end
369
+ let(:container_id) { container.id }
370
+
371
+ after { container.delete force: true }
372
+
373
+ it 'returns false', :vcr do
374
+ expect(result).to be false
375
+ end
376
+ end
377
+
378
+ context 'when the container does not exist', :vcr do
379
+ let(:container_id) { 'doesnotexist' }
380
+
381
+ it 'returns false' do
382
+ expect(result).to be false
383
+ end
384
+ end
385
+ end
386
+
387
+ describe '::tag' do
388
+ subject { described_class.tag id, tag }
389
+ let(:id) { Docker::Image.create('fromImage' => 'hawknewton/true').id }
390
+ let(:tag) { 'test/tag:1.2.3' }
391
+
392
+ before do
393
+ begin
394
+ Docker::Image.get(tag).remove 'force' => true
395
+ rescue Docker::Error::NotFoundError # rubocop:disable Lint/HandleExceptions
396
+ end
397
+ end
398
+
399
+ after do
400
+ Docker::Image.get(tag).remove 'force' => true
401
+ end
402
+
403
+ it 'should tag the image', :vcr do
404
+ subject
405
+ expect { Docker::Image.get 'test/tag:1.2.3' }.to_not raise_error
406
+ end
407
+ end
408
+ end
409
+ end
@@ -0,0 +1,32 @@
1
+ module Bigrig
2
+ describe Application do
3
+ it { is_expected.to respond_to :name }
4
+ it { is_expected.to respond_to :name= }
5
+
6
+ it { is_expected.to respond_to :containers }
7
+ it { is_expected.to respond_to :containers= }
8
+
9
+ describe '::from_json' do
10
+ subject { described_class.from_json json }
11
+
12
+ context 'given a simple file' do
13
+ let(:file) { 'single.json' }
14
+ let(:json) { Descriptor.read(test_file(file)).as_json }
15
+
16
+ its('containers.size') { is_expected.to eq 1 }
17
+
18
+ it 'should set the container name' do
19
+ expect(subject.containers[0].name).to eq 'single-test'
20
+ end
21
+
22
+ it 'should set the container tag' do
23
+ expect(subject.containers[0].tag).to eq '0.0.1'
24
+ end
25
+
26
+ it 'should set the container repo' do
27
+ expect(subject.containers[0].repo).to eq 'hawknewton/show-env'
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,115 @@
1
+ module Bigrig
2
+ describe Container do
3
+ it { is_expected.to respond_to :hosts= }
4
+ it { is_expected.to respond_to :hosts }
5
+ it { is_expected.to respond_to :links= }
6
+ it { is_expected.to respond_to :links }
7
+ it { is_expected.to respond_to :name }
8
+ it { is_expected.to respond_to :name= }
9
+ it { is_expected.to respond_to :path }
10
+ it { is_expected.to respond_to :path= }
11
+ it { is_expected.to respond_to :ports }
12
+ it { is_expected.to respond_to :ports= }
13
+ it { is_expected.to respond_to :repo }
14
+ it { is_expected.to respond_to :repo= }
15
+ it { is_expected.to respond_to :tag }
16
+ it { is_expected.to respond_to :tag= }
17
+ it { is_expected.to respond_to :volumes_from }
18
+ it { is_expected.to respond_to :volumes_from= }
19
+
20
+ it 'accepts volumes_from as an array' do
21
+ expect(Container.from_json(nil, 'volumes_from' => ['test']).volumes_from).to be_kind_of Array
22
+ end
23
+
24
+ it 'wraps a single volumes_from in an array' do
25
+ expect(Container.from_json(nil, 'volumes_from' => 'test').volumes_from).to be_kind_of Array
26
+ end
27
+
28
+ it 'accepts links as an array' do
29
+ expect(Container.from_json(nil, 'links' => ['machine:alias']).links).to be_kind_of Array
30
+ end
31
+
32
+ it 'wraps a single links in an array' do
33
+ expect(Container.from_json(nil, 'links' => 'machine:alias').links).to be_kind_of Array
34
+ end
35
+
36
+ it 'accepts hosts as an array' do
37
+ expect(Container.from_json(nil, 'links' => ['host:alias']).links).to be_kind_of Array
38
+ end
39
+
40
+ it 'wraps a single links in an array' do
41
+ expect(Container.from_json(nil, 'hosts' => 'host:alias').links).to be_kind_of Array
42
+ end
43
+
44
+ it 'depends on containers it mounts volumes from' do
45
+ expect(Container.from_json(nil, 'volumes_from' => 'test').dependencies).to eq ['test']
46
+ end
47
+
48
+ it 'depends on containers it links to' do
49
+ expect(Container.from_json(nil, 'links' => 'machine:alias').dependencies).to eq ['machine']
50
+ end
51
+
52
+ describe '#dependencies' do
53
+ it 'returns an empty array when no dependencies are present' do
54
+ expect(Container.from_json(nil, {}).dependencies).to eq []
55
+ end
56
+ end
57
+
58
+ describe '::from_json' do
59
+ subject { Container.from_json name, json }
60
+ let(:name) { 'name' }
61
+
62
+ context 'with an empty json document' do
63
+ let(:json) { {} }
64
+
65
+ its(:env) { is_expected.to be_empty }
66
+ its(:env) { is_expected.to be_a Hash }
67
+
68
+ its(:volumes_from) { is_expected.to be_empty }
69
+ its(:volumes_from) { is_expected.to be_a Array }
70
+
71
+ its(:ports) { is_expected.to be_a Array }
72
+ its(:ports) { is_expected.to be_empty }
73
+
74
+ end
75
+
76
+ context 'given json with a path' do
77
+ let(:json) { { 'path' => '/path/to/Dockerfile' } }
78
+
79
+ it 'sets the path' do
80
+ expect(subject.path).to eq '/path/to/Dockerfile'
81
+ end
82
+ end
83
+
84
+ context 'given json with env params' do
85
+ let(:json) { { 'env' => { 'NAME1' => 'VALUE1', 'NAME2' => 'VALUE2' } } }
86
+
87
+ it 'parses the environment variables' do
88
+ expect(subject.env).to include(
89
+ 'NAME1' => 'VALUE1',
90
+ 'NAME2' => 'VALUE2'
91
+ )
92
+ end
93
+
94
+ context 'that reference environment variables' do
95
+ let(:json) { { 'env' => { 'NAME1' => "\#{ENV['NAME1']}" } } }
96
+
97
+ before { ENV['NAME1'] = 'ENV1' }
98
+ after { ENV['NAME1'] = nil }
99
+
100
+ it 'retrieves the variable from the environemnt' do
101
+ expect(subject.env).to include('NAME1' => 'ENV1')
102
+ end
103
+ end
104
+ end
105
+
106
+ context 'given json with ports' do
107
+ let(:json) { { 'ports' => ['80:8080', '12345'] } }
108
+
109
+ it 'has ports' do
110
+ expect(subject.ports).to eq ['80:8080', '12345']
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,71 @@
1
+ module Bigrig
2
+ describe OutputParser do
3
+ describe '#parse' do
4
+ subject do
5
+ parser = described_class.new
6
+ [input].flatten.map { |i| parser.parse i }.last
7
+ end
8
+
9
+ let(:mock_io) { double IO }
10
+ let(:output) { subject }
11
+
12
+ context 'with input that has a stream' do
13
+ let(:input) { '{"stream": "value"}' }
14
+
15
+ it 'returns the value of stream' do
16
+ expect(output).to eq 'value'
17
+ end
18
+ end
19
+
20
+ context 'with input that has no id and a status' do
21
+ let(:input) { '{"status": "status_value"}' }
22
+
23
+ it 'returns the value of status with a linefeed' do
24
+ expect(output).to eq "status_value\n"
25
+ end
26
+ end
27
+
28
+ context 'with input that has an id, status, and progress' do
29
+ let(:input) { '{"id": "id", "status": "status", "progress" : "progress" }' }
30
+
31
+ it 'formats the output with a trailing carrage return' do
32
+ expect(output).to eq "id: status progress\r"
33
+ end
34
+ end
35
+
36
+ context 'when the id changes' do
37
+ let(:input) do
38
+ ['{"id": "id", "status": "status", "progress" : "progress" }',
39
+ '{"id": "new_id", "status": "status", "progress" : "progress" }']
40
+ end
41
+
42
+ it 'prints a linefeed' do
43
+ expect(output).to eq "\nnew_id: status progress\r"
44
+ end
45
+ end
46
+
47
+ context 'when the id noralizes after changing' do
48
+ let(:input) do
49
+ ['{"id": "id", "status": "status", "progress" : "progress" }',
50
+ '{"id": "new_id", "status": "status", "progress" : "progress" }',
51
+ '{"id": "new_id", "status": "status", "progress" : "progress" }']
52
+ end
53
+
54
+ it 'does not print a linefeed' do
55
+ expect(output).to eq "new_id: status progress\r"
56
+ end
57
+ end
58
+
59
+ context 'when a shorter message is returned with the same id' do
60
+ let(:input) do
61
+ ['{"id": "id", "status": "status", "progress" : "i am a much longer message" }',
62
+ '{"id": "id", "status": "status", "progress" : "progress" }']
63
+ end
64
+
65
+ it 'pads the output' do
66
+ expect(output).to eq "id: status progress \r"
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end