dry-dock 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.dockerignore +6 -0
- data/.pryrc +1 -0
- data/.rspec +2 -0
- data/Dockerfile +69 -0
- data/Gemfile +20 -0
- data/Gemfile.lock +102 -0
- data/LICENSE +22 -0
- data/README.md +75 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/bin/drydock +45 -0
- data/bin/json-test-consumer.rb +11 -0
- data/bin/json-test-producer.rb +25 -0
- data/bin/test-tar-writer-digest.rb +27 -0
- data/dry-dock.gemspec +135 -0
- data/examples/ruby-dsl.rb +14 -0
- data/examples/ruby-node-app-dsl.rb +128 -0
- data/examples/test-dsl.rb +9 -0
- data/examples/test.rb +46 -0
- data/lib/drydock/cli_flags.rb +46 -0
- data/lib/drydock/container_config.rb +75 -0
- data/lib/drydock/docker_api_patch.rb +176 -0
- data/lib/drydock/drydock.rb +65 -0
- data/lib/drydock/errors.rb +6 -0
- data/lib/drydock/file_manager.rb +26 -0
- data/lib/drydock/formatters.rb +13 -0
- data/lib/drydock/ignorefile_definition.rb +61 -0
- data/lib/drydock/image_repository.rb +50 -0
- data/lib/drydock/logger.rb +61 -0
- data/lib/drydock/object_caches/base.rb +24 -0
- data/lib/drydock/object_caches/filesystem_cache.rb +88 -0
- data/lib/drydock/object_caches/in_memory_cache.rb +52 -0
- data/lib/drydock/object_caches/no_cache.rb +38 -0
- data/lib/drydock/phase.rb +50 -0
- data/lib/drydock/phase_chain.rb +233 -0
- data/lib/drydock/plugins/apk.rb +31 -0
- data/lib/drydock/plugins/base.rb +15 -0
- data/lib/drydock/plugins/npm.rb +16 -0
- data/lib/drydock/plugins/package_manager.rb +30 -0
- data/lib/drydock/plugins/rubygems.rb +30 -0
- data/lib/drydock/project.rb +427 -0
- data/lib/drydock/runtime_options.rb +79 -0
- data/lib/drydock/stream_monitor.rb +54 -0
- data/lib/drydock/tar_writer.rb +36 -0
- data/lib/drydock.rb +35 -0
- data/spec/assets/MANIFEST +4 -0
- data/spec/assets/hello-world.txt +1 -0
- data/spec/assets/sample.tar +0 -0
- data/spec/assets/test.sh +3 -0
- data/spec/drydock/cli_flags_spec.rb +38 -0
- data/spec/drydock/container_config_spec.rb +230 -0
- data/spec/drydock/docker_api_patch_spec.rb +103 -0
- data/spec/drydock/drydock_spec.rb +25 -0
- data/spec/drydock/file_manager_spec.rb +53 -0
- data/spec/drydock/formatters_spec.rb +26 -0
- data/spec/drydock/ignorefile_definition_spec.rb +123 -0
- data/spec/drydock/image_repository_spec.rb +54 -0
- data/spec/drydock/object_caches/base_spec.rb +28 -0
- data/spec/drydock/object_caches/filesystem_cache_spec.rb +48 -0
- data/spec/drydock/object_caches/no_cache_spec.rb +62 -0
- data/spec/drydock/phase_chain_spec.rb +118 -0
- data/spec/drydock/phase_spec.rb +67 -0
- data/spec/drydock/plugins/apk_spec.rb +49 -0
- data/spec/drydock/plugins/base_spec.rb +13 -0
- data/spec/drydock/plugins/npm_spec.rb +26 -0
- data/spec/drydock/plugins/package_manager_spec.rb +12 -0
- data/spec/drydock/plugins/rubygems_spec.rb +53 -0
- data/spec/drydock/project_import_spec.rb +39 -0
- data/spec/drydock/project_spec.rb +156 -0
- data/spec/drydock/runtime_options_spec.rb +31 -0
- data/spec/drydock/stream_monitor_spec.rb +41 -0
- data/spec/drydock/tar_writer_spec.rb +27 -0
- data/spec/spec_helper.rb +47 -0
- data/spec/support/shared_examples/base_class.rb +3 -0
- data/spec/support/shared_examples/container_config.rb +12 -0
- data/spec/support/shared_examples/drydockfile.rb +6 -0
- metadata +223 -0
@@ -0,0 +1,230 @@
|
|
1
|
+
|
2
|
+
# Tests taken from specs at https://github.com/docker/docker/blob/master/runconfig/compare_test.go
|
3
|
+
RSpec.describe Drydock::ContainerConfig do
|
4
|
+
|
5
|
+
describe '#==' do
|
6
|
+
|
7
|
+
let(:ports1) { {"1111/tcp" => {}, "2222/tcp" => {}} }
|
8
|
+
let(:ports2) { {"3333/tcp" => {}, "4444/tcp" => {}} }
|
9
|
+
let(:ports3) { {"1111/tcp" => {}, "2222/tcp" => {}, "5555/tcp" => {}} }
|
10
|
+
|
11
|
+
let(:volumes1) { {"/test1" => {}} }
|
12
|
+
let(:volumes2) { {"/test2" => {}} }
|
13
|
+
let(:volumes3) { {"/test1" => {}, "/test3" => {}} }
|
14
|
+
|
15
|
+
let(:envs1) { ["ENV1=value1", "ENV2=value2"] }
|
16
|
+
let(:envs2) { ["ENV1=value1", "ENV3=value3"] }
|
17
|
+
|
18
|
+
let(:entrypoint1) { ['/bin/sh', '-c'] }
|
19
|
+
let(:entrypoint2) { ['/bin/sh', '-d'] }
|
20
|
+
let(:entrypoint3) { ['/bin/sh', '-c', 'echo'] }
|
21
|
+
|
22
|
+
let(:cmd1) { ['/bin/sh', '-c'] }
|
23
|
+
let(:cmd2) { ['/bin/sh', '-d'] }
|
24
|
+
let(:cmd3) { ['/bin/sh', '-c', 'echo'] }
|
25
|
+
|
26
|
+
let(:labels1) { {"LABEL1" => "value1", "LABEL2" => "value2"} }
|
27
|
+
let(:labels2) { {"LABEL1" => "value1", "LABEL2" => "value3"} }
|
28
|
+
let(:labels3) { {"LABEL1" => "value1", "LABEL2" => "value2", "LABEL3" => "value3"} }
|
29
|
+
|
30
|
+
context 'empty configs' do
|
31
|
+
it_behaves_like 'the same configs' do
|
32
|
+
let(:config1) { {} }
|
33
|
+
let(:config2) { {} }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'different hostnames, domain names, or image' do
|
38
|
+
it_behaves_like 'the same configs' do
|
39
|
+
let(:config1) {
|
40
|
+
{
|
41
|
+
Hostname: "host1",
|
42
|
+
Domainname: "domain1",
|
43
|
+
Image: "image1",
|
44
|
+
User: "user"
|
45
|
+
}
|
46
|
+
}
|
47
|
+
let(:config2) {
|
48
|
+
{
|
49
|
+
Hostname: "host2",
|
50
|
+
Domainname: "domain2",
|
51
|
+
Image: "image2",
|
52
|
+
User: "user"
|
53
|
+
}
|
54
|
+
}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'only OpenStdin' do
|
59
|
+
it_behaves_like 'the same configs' do
|
60
|
+
let(:config1) { {OpenStdin: false} }
|
61
|
+
let(:config2) { {OpenStdin: false} }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'only Env' do
|
66
|
+
it_behaves_like 'the same configs' do
|
67
|
+
let(:config1) { {Env: envs1.dup} }
|
68
|
+
let(:config2) { {Env: envs1.dup} }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'only Cmd' do
|
73
|
+
it_behaves_like 'the same configs' do
|
74
|
+
let(:config1) { {Cmd: cmd1.dup} }
|
75
|
+
let(:config2) { {Cmd: cmd1.dup} }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'only Labels' do
|
80
|
+
it_behaves_like 'the same configs' do
|
81
|
+
let(:config1) { {Labels: labels1.dup} }
|
82
|
+
let(:config2) { {Labels: labels1.dup} }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context 'only ExposedPorts' do
|
87
|
+
it_behaves_like 'the same configs' do
|
88
|
+
let(:config1) { {ExposedPorts: ports1.dup} }
|
89
|
+
let(:config2) { {ExposedPorts: ports1.dup} }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context 'only Entrypoint' do
|
94
|
+
it_behaves_like 'the same configs' do
|
95
|
+
let(:config1) { {Entrypoint: entrypoint1.dup} }
|
96
|
+
let(:config2) { {Entrypoint: entrypoint1.dup} }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'only Volumes' do
|
101
|
+
it_behaves_like 'the same configs' do
|
102
|
+
let(:config1) { {Volumes: volumes1.dup} }
|
103
|
+
let(:config2) { {Volumes: volumes1.dup} }
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
|
109
|
+
context 'with nil' do
|
110
|
+
it_behaves_like 'different configs' do
|
111
|
+
let(:config1) { {} }
|
112
|
+
let(:config2) { nil }
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'different users' do
|
117
|
+
it_behaves_like 'different configs' do
|
118
|
+
let(:config1) {
|
119
|
+
{
|
120
|
+
Hostname: "host1",
|
121
|
+
Domainname: "domain1",
|
122
|
+
Image: "image1",
|
123
|
+
User: "user1"
|
124
|
+
}
|
125
|
+
}
|
126
|
+
let(:config2) {
|
127
|
+
{
|
128
|
+
Hostname: "host1",
|
129
|
+
Domainname: "domain1",
|
130
|
+
Image: "image1",
|
131
|
+
User: "user2"
|
132
|
+
}
|
133
|
+
}
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
context 'differing OpenStdin' do
|
138
|
+
it_behaves_like 'different configs' do
|
139
|
+
let(:config1) { {OpenStdin: false} }
|
140
|
+
let(:config2) { {OpenStdin: true } }
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
context 'differing OpenStdin, reversed' do
|
145
|
+
it_behaves_like 'different configs' do
|
146
|
+
let(:config1) { {OpenStdin: true } }
|
147
|
+
let(:config2) { {OpenStdin: false} }
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
context 'differing Env' do
|
152
|
+
it_behaves_like 'different configs' do
|
153
|
+
let(:config1) { {Env: envs1} }
|
154
|
+
let(:config2) { {Env: envs2} }
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
context 'differing Cmd' do
|
159
|
+
it_behaves_like 'different configs' do
|
160
|
+
let(:config1) { {Cmd: cmd1} }
|
161
|
+
let(:config2) { {Cmd: cmd2} }
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
context 'differing Cmd parts' do
|
166
|
+
it_behaves_like 'different configs' do
|
167
|
+
let(:config1) { {Cmd: cmd1} }
|
168
|
+
let(:config2) { {Cmd: cmd3} }
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
context 'differing Labels' do
|
173
|
+
it_behaves_like 'different configs' do
|
174
|
+
let(:config1) { {Labels: labels1} }
|
175
|
+
let(:config2) { {Labels: labels2} }
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
context 'differing Labels' do
|
180
|
+
it_behaves_like 'different configs' do
|
181
|
+
let(:config1) { {Labels: labels1} }
|
182
|
+
let(:config2) { {Labels: labels3} }
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
context 'differing ExposedPorts' do
|
187
|
+
it_behaves_like 'different configs' do
|
188
|
+
let(:config1) { {ExposedPorts: ports1} }
|
189
|
+
let(:config2) { {ExposedPorts: ports2} }
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
context 'differing number of ExposedPorts' do
|
194
|
+
it_behaves_like 'different configs' do
|
195
|
+
let(:config1) { {ExposedPorts: ports1} }
|
196
|
+
let(:config2) { {ExposedPorts: ports3} }
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
context 'differing Entrypoint' do
|
201
|
+
it_behaves_like 'different configs' do
|
202
|
+
let(:config1) { {Entrypoint: entrypoint1} }
|
203
|
+
let(:config2) { {Entrypoint: entrypoint2} }
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
context 'differing Entrypoint parts' do
|
208
|
+
it_behaves_like 'different configs' do
|
209
|
+
let(:config1) { {Entrypoint: entrypoint1} }
|
210
|
+
let(:config2) { {Entrypoint: entrypoint3} }
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
context 'differing Volumes' do
|
215
|
+
it_behaves_like 'different configs' do
|
216
|
+
let(:config1) { {Volumes: volumes1} }
|
217
|
+
let(:config2) { {Volumes: volumes2} }
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
context 'differing number of Volumes' do
|
222
|
+
it_behaves_like 'different configs' do
|
223
|
+
let(:config1) { {Volumes: volumes1} }
|
224
|
+
let(:config2) { {Volumes: volumes3} }
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
end
|
229
|
+
|
230
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
|
2
|
+
RSpec.describe Docker::Container do
|
3
|
+
|
4
|
+
describe '#archive_put' do
|
5
|
+
let(:tar) { Docker::Util.create_tar('/some_file' => 'contents_123') }
|
6
|
+
let(:container) { described_class.create('Image' => 'alpine:latest', 'Cmd' => ['/bin/sh']) }
|
7
|
+
let(:image) { container.commit }
|
8
|
+
let(:run_container) { image.run(['/bin/cat', '/some_file']).tap(&:wait) }
|
9
|
+
let(:output) { run_container.streaming_logs(stdout: true, stderr: true) }
|
10
|
+
|
11
|
+
after(:each) { container.remove }
|
12
|
+
|
13
|
+
context 'when given a tar stream' do
|
14
|
+
after do
|
15
|
+
run_container.remove
|
16
|
+
image.remove
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'has a file in the container' do
|
20
|
+
container.archive_put('/') do |output|
|
21
|
+
output.write(tar)
|
22
|
+
end
|
23
|
+
expect(output).to start_with('contents_123')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#archive_get' do
|
29
|
+
let(:container) { described_class.create('Image' => 'alpine:latest', 'Cmd' => ['/bin/touch', '/real_file']) }
|
30
|
+
|
31
|
+
after(:each) { container.remove }
|
32
|
+
|
33
|
+
context 'when the file does not exist' do
|
34
|
+
it 'is not found' do
|
35
|
+
container.start
|
36
|
+
container.wait
|
37
|
+
expect { container.archive_get('/not_a_file') { |c| c } }.to raise_error(Docker::Error::NotFoundError)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'when the file is found' do
|
42
|
+
it 'yields in chunks' do
|
43
|
+
container.start
|
44
|
+
container.wait
|
45
|
+
|
46
|
+
chunks = StringIO.new
|
47
|
+
container.archive_get('/real_file') { |c| chunks << c }
|
48
|
+
expect(chunks.string).to start_with("real_file\0\0")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '#archive_head' do
|
54
|
+
let(:container) { described_class.create('Image' => 'alpine:latest', 'Cmd' => ['/bin/touch', '/real_file']) }
|
55
|
+
|
56
|
+
before(:each) { container.tap(&:start).tap(&:wait) }
|
57
|
+
after(:each) { container.remove }
|
58
|
+
|
59
|
+
context 'when the file does not exist' do
|
60
|
+
it 'is not found' do
|
61
|
+
expect(container.archive_head('/not_a_file')).to eq(nil)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'when the file is found' do
|
66
|
+
it 'returns the stat results' do
|
67
|
+
stat = container.archive_head('/real_file')
|
68
|
+
expect(stat).to be_a(Docker::ContainerPathStat)
|
69
|
+
expect(stat.name).to eq('real_file')
|
70
|
+
expect(stat.size).to eq(0)
|
71
|
+
|
72
|
+
expect(stat.mode.file_mode).to eq(0644)
|
73
|
+
expect(stat.mode.regular?).to be_truthy
|
74
|
+
expect(stat.mode.directory?).to be_falsey
|
75
|
+
|
76
|
+
expect(stat.mode.flags).to be_empty
|
77
|
+
expect(stat.mode.short_flags).to be_empty
|
78
|
+
expect(stat.mode.to_s).to eq('')
|
79
|
+
|
80
|
+
expect(stat.mtime).not_to be_nil
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'when a link is found' do
|
85
|
+
it 'returns the stat results' do
|
86
|
+
stat = container.archive_head('/etc/mtab')
|
87
|
+
expect(stat).to be_a(Docker::ContainerPathStat)
|
88
|
+
expect(stat).to respond_to(:link?)
|
89
|
+
|
90
|
+
expect(stat.mode.link?).to be_truthy
|
91
|
+
expect(stat.link?).to be_truthy
|
92
|
+
expect(stat.link_target).to eq('/proc/mounts')
|
93
|
+
|
94
|
+
expect(stat.mode.flags).to eq([:link])
|
95
|
+
expect(stat.mode.short_flags).to eq(['L'])
|
96
|
+
expect(stat.mode.to_s).to eq('L')
|
97
|
+
|
98
|
+
expect(stat.mtime).not_to be_nil
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
|
2
|
+
RSpec.describe Drydock do
|
3
|
+
|
4
|
+
describe '.build' do
|
5
|
+
|
6
|
+
let(:opts) { Hash.new }
|
7
|
+
|
8
|
+
context 'with an empty script' do
|
9
|
+
let(:script) { "" }
|
10
|
+
it_behaves_like 'a Drydockfile'
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'from the scratch image' do
|
14
|
+
let(:script) { "from 'scratch'" }
|
15
|
+
it_behaves_like 'a Drydockfile'
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'from an Alpine Linux image' do
|
19
|
+
let(:script) { "from 'gliderlabs/alpine'" }
|
20
|
+
it_behaves_like 'a Drydockfile'
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
|
2
|
+
RSpec.describe Drydock::FileManager do
|
3
|
+
|
4
|
+
describe '.find' do
|
5
|
+
|
6
|
+
let(:ignore_none) do
|
7
|
+
double('Ignore Nothing').tap do |file|
|
8
|
+
allow(file).to receive(:match?).and_return(false)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'current directory with default options' do
|
13
|
+
subject { described_class.find('.', ignore_none) }
|
14
|
+
|
15
|
+
it { is_expected.to respond_to(:each) }
|
16
|
+
it { is_expected.to include('bin/drydock') }
|
17
|
+
it { is_expected.to have_at_least(30).items }
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'a subdirectory' do
|
21
|
+
context 'with default options' do
|
22
|
+
subject { described_class.find('spec/assets', ignore_none) }
|
23
|
+
it { is_expected.to include('MANIFEST') }
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'with prepend path' do
|
27
|
+
subject { described_class.find('spec/assets', ignore_none, prepend_path: true) }
|
28
|
+
it { is_expected.to include('spec/assets/MANIFEST') }
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'with trailing slashes with prepend path' do
|
32
|
+
subject { described_class.find('spec/assets/', ignore_none, prepend_path: true) }
|
33
|
+
it { is_expected.to include('spec/assets/MANIFEST') }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'an absolute path' do
|
38
|
+
context 'with default options' do
|
39
|
+
let(:abs_path) { File.expand_path('spec/assets') }
|
40
|
+
subject { described_class.find(abs_path, ignore_none) }
|
41
|
+
it { is_expected.to include('MANIFEST') }
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'with prepend path' do
|
45
|
+
let(:abs_path) { File.expand_path('spec/assets') }
|
46
|
+
subject { described_class.find(abs_path, ignore_none, prepend_path: true) }
|
47
|
+
it { is_expected.to include(abs_path + '/MANIFEST') }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
RSpec.describe Drydock::Formatters do
|
3
|
+
|
4
|
+
describe '.number' do
|
5
|
+
context 'a small integer' do
|
6
|
+
subject { described_class.number(2) }
|
7
|
+
it { is_expected.to eq('2') }
|
8
|
+
end
|
9
|
+
|
10
|
+
context 'a large integer' do
|
11
|
+
subject { described_class.number(5196714) }
|
12
|
+
it { is_expected.to eq('5,196,714') }
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'a large decimal' do
|
16
|
+
subject { described_class.number(1024.66) }
|
17
|
+
it { is_expected.to eq('1,024.66') }
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'a negative decimal' do
|
21
|
+
subject { described_class.number(-24.52) }
|
22
|
+
it { is_expected.to eq('-24.52') }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
|
2
|
+
RSpec.describe Drydock::IgnorefileDefinition do
|
3
|
+
|
4
|
+
describe '.new' do
|
5
|
+
it 'raises an error when no filename is provided' do
|
6
|
+
expect { described_class.new() }.to raise_error(ArgumentError)
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'raises an error when a non-existant file is provided' do
|
10
|
+
expect { described_class.new('file_does_not_exist') }.not_to raise_error
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'when given a file handle with two positive rules' do
|
15
|
+
let(:file) { StringIO.new(".gitignore\nvendor/ruby") }
|
16
|
+
let(:ifd) { described_class.new(file) }
|
17
|
+
|
18
|
+
describe '.new' do
|
19
|
+
subject { ifd }
|
20
|
+
it 'reads the entire file' do
|
21
|
+
expect(file.eof?).to be(false)
|
22
|
+
expect(file.pos).to eq(0)
|
23
|
+
|
24
|
+
expect { subject }.not_to raise_error
|
25
|
+
expect(file.eof?).to be(true)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '#match?' do
|
30
|
+
context 'for a filename appearing in the ignore file' do
|
31
|
+
subject { ifd.match?('.gitignore') }
|
32
|
+
it { is_expected.to be(true) }
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'for a directory name not exactly appearing in the ignore file' do
|
36
|
+
subject { ifd.match?('vendor') }
|
37
|
+
it { is_expected.to be(false) }
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'for a directory name appearing in the ignore file' do
|
41
|
+
subject { ifd.match?('vendor/ruby') }
|
42
|
+
it { is_expected.to be(true) }
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'for a regular filename NOT appearing in the ignore file' do
|
46
|
+
subject { ifd.match?('Gemfile') }
|
47
|
+
it { is_expected.to be(false) }
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'for a dotfile NOT appearing in the ignore' do
|
51
|
+
subject { ifd.match?('.rspec') }
|
52
|
+
it { is_expected.to be(false) }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '#size' do
|
57
|
+
subject { ifd.size }
|
58
|
+
it { is_expected.to eq(2) }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'when given a file handle with a negative rule' do
|
63
|
+
let(:file) { StringIO.new("!.gitignore") }
|
64
|
+
let(:ifd) { described_class.new(file) }
|
65
|
+
|
66
|
+
describe '#match?' do
|
67
|
+
context 'for a filename excluded in the ignore file' do
|
68
|
+
subject { ifd.match?('.gitignore') }
|
69
|
+
it { is_expected.to be(false) }
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'for a filename not excluded in the ignore file' do
|
73
|
+
subject { ifd.match?('Gemfile') }
|
74
|
+
it { is_expected.to be(false) }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe '#size' do
|
79
|
+
subject { ifd.size }
|
80
|
+
it { is_expected.to eq(1) }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'when given an empty file handle with automatic dotfile handling' do
|
85
|
+
let(:file) { StringIO.new("") }
|
86
|
+
let(:ifd) { described_class.new(file, dotfiles: true) }
|
87
|
+
|
88
|
+
describe '#match?' do
|
89
|
+
context 'for a dotfile' do
|
90
|
+
subject { ifd.match?('.gitignore') }
|
91
|
+
it { is_expected.to eq(true) }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe '#size' do
|
96
|
+
subject { ifd.size }
|
97
|
+
it { is_expected.to eq(0) }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'when given a file handle with a wildcard' do
|
102
|
+
let(:file) { StringIO.new("Gemfile*") }
|
103
|
+
let(:ifd) { described_class.new(file) }
|
104
|
+
|
105
|
+
describe '#match?' do
|
106
|
+
context 'for an exact match' do
|
107
|
+
subject { ifd.match?('Gemfile')}
|
108
|
+
it { is_expected.to eq(true) }
|
109
|
+
end
|
110
|
+
|
111
|
+
context 'for a substring prefix match' do
|
112
|
+
subject { ifd.match?('Gemfile.lock') }
|
113
|
+
it { is_expected.to eq(true) }
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'for a substring suffix match' do
|
117
|
+
subject { ifd.match?('NotGemfile') }
|
118
|
+
it { is_expected.to eq(false) }
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
|
2
|
+
RSpec.describe Drydock::ImageRepository do
|
3
|
+
|
4
|
+
describe '.all' do
|
5
|
+
it 'completes without an error' do
|
6
|
+
expect { described_class.all }.not_to raise_error
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '.dangling' do
|
11
|
+
it 'completes without an error' do
|
12
|
+
expect { described_class.dangling }.not_to raise_error
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '.each' do
|
17
|
+
it 'returns each image' do
|
18
|
+
described_class.each do |image|
|
19
|
+
expect(image).to be_a(Docker::Image)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '.find_by_config' do
|
25
|
+
let(:cmd) { '/bin/ls -l' }
|
26
|
+
let(:image) { Docker::Image.create(fromImage: 'alpine', tag: 'latest') }
|
27
|
+
let(:container) { image.run(cmd).tap(&:wait) }
|
28
|
+
let(:run_image) { container.commit }
|
29
|
+
|
30
|
+
after(:each) do
|
31
|
+
container.remove
|
32
|
+
run_image.remove
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'returns the latest image' do
|
36
|
+
expect(run_image.id).not_to be_nil
|
37
|
+
|
38
|
+
build_config = Drydock::ContainerConfig.from(
|
39
|
+
Cmd: cmd.to_s.split(/\s+/),
|
40
|
+
Tty: false,
|
41
|
+
Image: image.id,
|
42
|
+
Env: nil
|
43
|
+
)
|
44
|
+
|
45
|
+
possible_images = described_class.select_by_config(build_config)
|
46
|
+
expect(possible_images).not_to be_empty
|
47
|
+
|
48
|
+
found_image = described_class.find_by_config(build_config)
|
49
|
+
expect(found_image).not_to be_nil
|
50
|
+
expect(found_image.id).to eq(run_image.id)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
RSpec.describe Drydock::ObjectCaches::Base do
|
3
|
+
|
4
|
+
describe '.new' do
|
5
|
+
it 'takes no arguments' do
|
6
|
+
expect { described_class.new }.not_to raise_error
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '#fetch' do
|
11
|
+
it 'is not implemented' do
|
12
|
+
expect { described_class.new.fetch(:foo) }.to raise_error(NotImplementedError)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#get' do
|
17
|
+
it 'is not implemented' do
|
18
|
+
expect { described_class.new.get(:foo) }.to raise_error(NotImplementedError)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#set' do
|
23
|
+
it 'is not implemented' do
|
24
|
+
expect { described_class.new.set(:foo, :some_value) }.to raise_error(NotImplementedError)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
|
2
|
+
RSpec.describe Drydock::ObjectCaches::FilesystemCache do
|
3
|
+
include FakeFS::SpecHelpers # mock the filesystem
|
4
|
+
|
5
|
+
describe '.new' do
|
6
|
+
it 'can take zero or one argument' do
|
7
|
+
expect { described_class.new }.not_to raise_error
|
8
|
+
expect { described_class.new("/tmp/drydock-spec") }.not_to raise_error
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'creates the cache directory' do
|
12
|
+
described_class.new("/tmp/drydock-spec")
|
13
|
+
expect(File.exist?("/tmp/drydock-spec/cache")).to eq(true)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#fetch' do
|
18
|
+
let(:cache) { described_class.new("/tmp/drydock-spec") }
|
19
|
+
|
20
|
+
it 'uses an existing value, when one is already set' do
|
21
|
+
cache.set('hello_world', 'Hello, World!')
|
22
|
+
expect(cache.fetch('hello_world') { 'Backup Value' }).to eq('Hello, World!')
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'invokes the writer if no value is already set' do
|
26
|
+
expect(cache.fetch('hello_world') { 'Backup Value' }).to eq('Backup Value')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#get, #set' do
|
31
|
+
let(:cache) { described_class.new("/tmp/drydock-spec") }
|
32
|
+
|
33
|
+
context 'when used in value form' do
|
34
|
+
it 'persists the cache entry' do
|
35
|
+
expect { cache.set('hello_world', 'Hello, World!') }.not_to raise_error
|
36
|
+
expect(cache.get('hello_world')).to eq('Hello, World!')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'when used in block form' do
|
41
|
+
it 'persists the cache entry' do
|
42
|
+
expect { cache.set('bye_world') { |f| f.write 'Goodbye, World!' } }.not_to raise_error
|
43
|
+
expect(cache.get('bye_world')).to eq('Goodbye, World!')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|