construi 0.30.0 → 0.31.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,251 @@
1
+
2
+ require 'spec_helper'
3
+
4
+ require 'securerandom'
5
+
6
+ RSpec.describe Construi::Image do
7
+ let(:id) { SecureRandom.hex(16) }
8
+ let(:docker_image) { instance_double(Docker::Image, :id => id).as_null_object }
9
+ let!(:docker_image_class) { class_spy(Docker::Image).as_stubbed_const }
10
+ let!(:container_class) { class_spy(Construi::Container).as_stubbed_const }
11
+
12
+ subject(:image) { Construi::Image.wrap(docker_image) }
13
+
14
+ describe '#id' do
15
+ subject { image.id }
16
+ it { is_expected.to eq(id) }
17
+ end
18
+
19
+ describe '#delete' do
20
+ subject! { image.delete }
21
+ it { expect(docker_image).to have_received(:delete) }
22
+ end
23
+
24
+ describe '#tagged?' do
25
+ before { allow(docker_image).to receive(:info).and_return({ 'RepoTags' => tag }) }
26
+
27
+ subject { image.tagged? }
28
+
29
+ context 'tagged' do
30
+ let(:tag) { 'tagged:latest' }
31
+ it { is_expected.to be(true) }
32
+ end
33
+
34
+ context 'not tagged' do
35
+ let(:tag) { '<none>:<none>' }
36
+ it { is_expected.to be(false) }
37
+ end
38
+ end
39
+
40
+ describe '#run' do
41
+ let(:cmd) { 'cmd1' }
42
+ let(:env) { ['VAR1=VALUE1'] }
43
+ let!(:container) { class_spy(Construi::Container).as_stubbed_const }
44
+
45
+ subject! { image.run(cmd, env) }
46
+
47
+ it { expect(container).to have_received(:run).with(image, cmd, env) }
48
+ end
49
+
50
+ describe '#insert_local' do
51
+ let(:host) { '/path/host' }
52
+ let(:container) { '/path/on/container' }
53
+ let(:permissions) { nil }
54
+ let(:file) { Construi::Config::Files::File.new host, container, permissions }
55
+
56
+ before { allow(docker_image).to receive(:info).and_return({ 'RepoTags' => '<none>:<none>' }) }
57
+ before { allow(docker_image).to receive(:insert_local).and_return(docker_image) }
58
+ before { allow(container_class).to receive(:run).and_return image }
59
+
60
+ subject! { image.insert_local file }
61
+
62
+ context 'no permissions' do
63
+ it { expect(subject.id).to eq(id) }
64
+ it do
65
+ expect(docker_image)
66
+ .to have_received(:insert_local)
67
+ .with 'localPath' => host, 'outputPath' => container
68
+ end
69
+ it { expect(container_class).to have_received(:run).with(image, "ls -l #{container}", []) }
70
+ end
71
+
72
+ context 'with permissions' do
73
+ let(:permissions) { '0600' }
74
+
75
+ it { expect(subject.id).to eq(id) }
76
+ it do
77
+ expect(docker_image)
78
+ .to have_received(:insert_local)
79
+ .with 'localPath' => host, 'outputPath' => container
80
+ end
81
+ it do
82
+ expect(container_class)
83
+ .to have_received(:run)
84
+ .with(image, "chmod -R #{permissions} #{container}", [])
85
+ end
86
+ it { expect($stdout.string).to include(" > chmod -R #{permissions} #{container}") }
87
+ it { expect(container_class).to have_received(:run).with(image, "ls -l #{container}", []) }
88
+ end
89
+
90
+ end
91
+
92
+ describe '.from' do
93
+ Config = Struct.new :image, :build, :files
94
+
95
+ let(:image) { nil }
96
+ let(:build) { nil }
97
+ let(:files) { [] }
98
+ let(:config) { Config.new image, build, files }
99
+
100
+ [:create, :build_from_dir].each do |m|
101
+ before { allow(docker_image_class).to receive(m).and_return docker_image }
102
+ end
103
+
104
+ subject(:from) { -> { Construi::Image.from(config) } }
105
+
106
+ context 'when build' do
107
+ let(:build) { 'build/dir' }
108
+
109
+ subject! { from.call }
110
+
111
+ it { expect(docker_image_class).to have_received(:build_from_dir) }
112
+ end
113
+
114
+ context 'when image' do
115
+ let(:image) { 'image:latest' }
116
+
117
+ subject! { from.call }
118
+
119
+ it { expect(docker_image_class).to have_received(:create) }
120
+ end
121
+
122
+ context 'when invalid' do
123
+ it { expect { from.call }.to raise_error(Construi::Image::Error, /Invalid image configuration/) }
124
+ end
125
+
126
+ context 'when files' do
127
+ let(:image) { 'image:latest' }
128
+ before { allow(docker_image).to receive(:info).and_return({ 'RepoTags' => image }) }
129
+
130
+ let(:host) { '/path/host' }
131
+ let(:container) { '/path/on/container' }
132
+ let(:permissions) { nil }
133
+
134
+ let(:files) { [ Construi::Config::Files::File.new(host, container, permissions) ] }
135
+
136
+ subject! { from.call }
137
+
138
+ it { expect(docker_image_class).to have_received(:create) }
139
+ it do
140
+ expect(docker_image)
141
+ .to have_received(:insert_local)
142
+ .with 'localPath' => host, 'outputPath' => container
143
+ end
144
+ it { expect($stdout.string).to include("\nCopying #{host} to #{container}...".green) }
145
+ end
146
+ end
147
+
148
+ describe '.build' do
149
+ let(:build_dir) { 'etc/docker' }
150
+
151
+ before do
152
+ allow(docker_image_class)
153
+ .to receive(:build_from_dir)
154
+ .with(build_dir, :rm => 0)
155
+ .and_yield('{"stream":"msg1"}')
156
+ .and_yield('{"stream":"msg2"}')
157
+ .and_return docker_image
158
+ end
159
+
160
+ subject! { Construi::Image.build(build_dir) }
161
+
162
+ it {
163
+ expect(docker_image_class)
164
+ .to have_received(:build_from_dir).with(build_dir, :rm => 0)
165
+ }
166
+
167
+ it { expect(docker_image).to have_received(:refresh!) }
168
+
169
+ it 'outputs building message' do
170
+ building_msg = "Building image: 'etc/docker'...".green
171
+ expect($stdout.string).to include("\n#{building_msg}\n")
172
+ end
173
+
174
+ it 'outputs build status messages' do
175
+ expect($stdout.string).to include("msg1\nmsg2\n")
176
+ end
177
+ end
178
+
179
+ describe '.create' do
180
+ let(:image_tag) { 'tagged:latest' }
181
+
182
+ before do
183
+ allow(docker_image_class)
184
+ .to receive(:create)
185
+ .with('fromImage' => image_tag)
186
+ .and_yield('{"id":"id","status":"msg1"}')
187
+ .and_yield('{"id":"id","status":"msg2","progressDetail":{}}')
188
+ .and_yield('{"id":"id","status":"msg3","progressDetail":{"progress":"====>"}}')
189
+ .and_yield('{"status":"msg4"}')
190
+ .and_return docker_image
191
+ end
192
+
193
+ subject! { Construi::Image.create(image_tag) }
194
+
195
+ it { expect(docker_image_class).to have_received(:create).with('fromImage' => image_tag) }
196
+ it { expect(docker_image).to have_received(:refresh!) }
197
+
198
+ it 'outputs creating message' do
199
+ creating_msg = "Creating image: 'tagged:latest'...".green
200
+ expect($stdout.string).to include("\n#{creating_msg}\n")
201
+ end
202
+
203
+ it 'outputs create status messages' do
204
+ expect($stdout.string).to include("id: msg1\nid: msg2\nmsg4\n")
205
+ end
206
+ end
207
+ end
208
+
209
+ RSpec.describe Construi::IntermediateImage do
210
+ let(:image) { instance_double(Construi::Image).as_null_object }
211
+ let(:second_image) { instance_double(Construi::Image).as_null_object }
212
+
213
+ before do
214
+ allow(image)
215
+ .to receive(:run)
216
+ .and_return image
217
+ end
218
+
219
+ before { allow(image).to receive(:run).and_return second_image }
220
+ before do
221
+ [image, second_image].each do |i|
222
+ allow(i).to receive(:tagged?).and_return false
223
+ end
224
+ end
225
+
226
+ subject(:intermediate_image) { Construi::IntermediateImage.seed(image) }
227
+
228
+ describe '#run' do
229
+ let(:cmd) { 'cmd1' }
230
+ let(:env) { ['VAR1=VALUE1'] }
231
+
232
+ context "single run" do
233
+ subject! { intermediate_image.run(cmd, env) }
234
+
235
+ it { expect(image).to have_received(:run).with(cmd, env) }
236
+ it { expect(image).to_not have_received(:delete) }
237
+ it { expect(second_image).to_not have_received(:delete) }
238
+ end
239
+
240
+ context "double run" do
241
+ subject! { intermediate_image.run(cmd, env).run(cmd, env) }
242
+
243
+ it { expect(second_image).to have_received(:run).with(cmd, env) }
244
+ it { expect(second_image).to have_received(:delete) }
245
+ end
246
+ end
247
+
248
+
249
+
250
+ end
251
+
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Construi::Runner do
4
+ let(:config_yaml) { '{}' }
5
+ let(:config) { Construi::Config.load(config_yaml) }
6
+
7
+ let(:image) { instance_double(Construi::Image).as_null_object }
8
+ let!(:image_class) { class_spy(Construi::Image).as_stubbed_const }
9
+
10
+ before { allow(image_class).to receive(:from).and_return image }
11
+
12
+ subject(:runner) { Construi::Runner.new(config) }
13
+
14
+ describe '#run' do
15
+ let(:config_yaml) do
16
+ <<-YAML
17
+ image: image:latest
18
+ targets:
19
+ target1: cmd1
20
+ YAML
21
+ end
22
+
23
+ let(:targets) { ['target1'] }
24
+
25
+ let!(:docker) { class_spy(Docker).as_stubbed_const }
26
+
27
+ before { allow(docker).to receive(:options).and_return({}) }
28
+
29
+ before { allow(image).to receive(:run).and_return image }
30
+ before { allow(image).to receive(:tagged?).and_return false }
31
+
32
+ subject! { runner.run(targets) }
33
+
34
+ it { expect(docker).to have_received(:validate_version!) }
35
+ it { expect(image).to have_received(:run).with('cmd1', []) }
36
+ it { expect(image).to have_received(:delete) }
37
+
38
+ it { expect($stdout.string).to include('Running target1...'.green) }
39
+ it { expect($stdout.string).to include(' > cmd1'.green) }
40
+ end
41
+
42
+
43
+ end
44
+
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Construi::Target do
4
+ let(:parent_config) { Construi::Config.load('{}') }
5
+ let(:target_config) do
6
+ cfg = {}
7
+ cfg['image'] = image unless image.nil?
8
+ cfg['build'] = build unless build.nil?
9
+
10
+ Construi::Config::Target.new(cfg, parent_config)
11
+ end
12
+
13
+ subject(:target) { Construi::Target.new nil, target_config }
14
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Construi do
4
+ describe 'run' do
5
+ let(:targets) { ['target1', 'target2'] }
6
+ let(:config) { { 'image' => 'image:latest' } }
7
+
8
+ let(:runner) { instance_double(Construi::Runner).as_null_object }
9
+ let(:runner_class) { class_double(Construi::Runner).as_stubbed_const }
10
+
11
+ before { allow(Construi::Config).to receive(:load_file).with('construi.yml').and_return(config) }
12
+ before { allow(runner_class).to receive(:new).with(config).and_return runner }
13
+
14
+ subject! { Construi.run(targets) }
15
+
16
+ it { expect(runner_class).to have_received(:new).with(config) }
17
+ it { expect(runner).to have_received(:run).with(targets) }
18
+ end
19
+ end
20
+
@@ -0,0 +1,132 @@
1
+ require 'spec_helper'
2
+
3
+ require 'securerandom'
4
+
5
+ RSpec.describe Construi::Container do
6
+ let!(:docker_container_class) { class_spy(Docker::Container).as_stubbed_const }
7
+ let(:docker_container) do
8
+ instance_double(Docker::Container, :id => SecureRandom.hex(16)).as_null_object
9
+ end
10
+
11
+ let(:image_id) { SecureRandom.hex(16) }
12
+ let(:image) { instance_double(Docker::Image, :id => image_id).as_null_object }
13
+
14
+ let(:container) { Construi::Container.wrap docker_container }
15
+
16
+ describe '#delete' do
17
+ subject! { container.delete }
18
+
19
+ it { expect(docker_container).to have_received(:delete) }
20
+ end
21
+
22
+ describe '#attach_stdout' do
23
+ subject(:attach_stdout) { -> { container.attach_stdout } }
24
+
25
+ context 'when attached succesfully' do
26
+ before do
27
+ allow(docker_container)
28
+ .to receive(:attach)
29
+ .and_yield('stream', 'msg1')
30
+ .and_yield('stream', 'msg2')
31
+ end
32
+
33
+ subject! { attach_stdout.call }
34
+
35
+ it { expect(docker_container).to have_received(:attach).with(:stream => true, :logs => true) }
36
+ it { expect($stdout.string).to include("msg1\nmsg2") }
37
+ end
38
+
39
+ context 'when attach times out' do
40
+ before do
41
+ allow(docker_container)
42
+ .to receive(:attach)
43
+ .and_raise(Docker::Error::TimeoutError.new)
44
+ end
45
+
46
+ subject! { attach_stdout.call }
47
+
48
+ it { expect($stdout.string).to include('Failed to attach to stdout'.yellow) }
49
+ end
50
+ end
51
+
52
+ describe '#commit' do
53
+ before { allow(docker_container).to receive(:commit).and_return image }
54
+
55
+ subject! { container.commit }
56
+
57
+ it { expect(docker_container).to have_received(:commit) }
58
+ it { is_expected.to eq(Construi::Image.wrap(image)) }
59
+ end
60
+
61
+ describe '#run' do
62
+ before { allow(docker_container).to receive(:wait).and_return({'StatusCode' => status_code}) }
63
+ before { allow(docker_container).to receive(:commit).and_return image }
64
+
65
+ subject(:run) { -> { container.run } }
66
+
67
+ context 'when command succeeds' do
68
+ let(:status_code) { 0 }
69
+
70
+ subject! { run.call }
71
+
72
+ it { expect(docker_container).to have_received(:start) }
73
+ it { expect(docker_container).to have_received(:attach).with(:stream => true, :logs => true) }
74
+ it { expect(docker_container).to have_received(:wait) }
75
+ it { expect(docker_container).to have_received(:commit) }
76
+ it { is_expected.to eq(Construi::Image.wrap(image)) }
77
+ end
78
+
79
+ context 'when command fails' do
80
+ let(:status_code) { 1 }
81
+
82
+ it { expect { run.call }.to raise_error Construi::Container::Error, /status code: 1/}
83
+ end
84
+ end
85
+
86
+ describe '.create' do
87
+ let(:cmd) { 'cmd1 p1 p2' }
88
+ let(:env) { ['ENV1=VAL1', 'ENV2=VAL2'] }
89
+ let(:pwd) { '/project/dir' }
90
+
91
+ before { allow(docker_container_class).to receive(:create).and_return docker_container }
92
+ before { allow(Dir).to receive(:pwd).and_return(pwd) }
93
+
94
+ subject! { Construi::Container::create(image, cmd, env) }
95
+
96
+ it do
97
+ expect(docker_container_class).to have_received(:create).with( {
98
+ 'Cmd' => ['cmd1', 'p1', 'p2' ],
99
+ 'Image' => image_id,
100
+ 'Env' => env.to_json,
101
+ 'Tty' => false,
102
+ 'WorkingDir' => '/var/workspace',
103
+ 'HostConfig' => { 'Binds' => ["#{pwd}:/var/workspace"] }
104
+ } )
105
+ end
106
+ it { is_expected.to eq(Construi::Container.wrap docker_container) }
107
+ end
108
+
109
+ describe '.run' do
110
+ let(:cmd) { 'cmd1 p1 p2' }
111
+
112
+ before { allow(docker_container_class).to receive(:create).and_return docker_container }
113
+ before { allow(docker_container).to receive(:wait).and_return({'StatusCode' => 0}) }
114
+ before { allow(docker_container).to receive(:commit).and_return image }
115
+
116
+ subject! { Construi::Container.run(image, cmd, []) }
117
+
118
+ it do
119
+ expect(docker_container_class).to have_received(:create).with(
120
+ hash_including( {
121
+ 'Cmd' => ['cmd1', 'p1', 'p2'],
122
+ 'Image' => image_id
123
+ }))
124
+ end
125
+ it { is_expected.to eq(Construi::Image.wrap image) }
126
+ it { expect(docker_container).to have_received(:start) }
127
+ it { expect(docker_container).to have_received(:commit) }
128
+ it { expect(docker_container).to have_received(:delete) }
129
+ end
130
+
131
+ end
132
+