dry-dock 0.1.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.
- 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
|