construi 0.30.0 → 0.31.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.
@@ -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
+