dry-dock 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +7 -0
  2. data/.dockerignore +6 -0
  3. data/.pryrc +1 -0
  4. data/.rspec +2 -0
  5. data/Dockerfile +69 -0
  6. data/Gemfile +20 -0
  7. data/Gemfile.lock +102 -0
  8. data/LICENSE +22 -0
  9. data/README.md +75 -0
  10. data/Rakefile +53 -0
  11. data/VERSION +1 -0
  12. data/bin/drydock +45 -0
  13. data/bin/json-test-consumer.rb +11 -0
  14. data/bin/json-test-producer.rb +25 -0
  15. data/bin/test-tar-writer-digest.rb +27 -0
  16. data/dry-dock.gemspec +135 -0
  17. data/examples/ruby-dsl.rb +14 -0
  18. data/examples/ruby-node-app-dsl.rb +128 -0
  19. data/examples/test-dsl.rb +9 -0
  20. data/examples/test.rb +46 -0
  21. data/lib/drydock/cli_flags.rb +46 -0
  22. data/lib/drydock/container_config.rb +75 -0
  23. data/lib/drydock/docker_api_patch.rb +176 -0
  24. data/lib/drydock/drydock.rb +65 -0
  25. data/lib/drydock/errors.rb +6 -0
  26. data/lib/drydock/file_manager.rb +26 -0
  27. data/lib/drydock/formatters.rb +13 -0
  28. data/lib/drydock/ignorefile_definition.rb +61 -0
  29. data/lib/drydock/image_repository.rb +50 -0
  30. data/lib/drydock/logger.rb +61 -0
  31. data/lib/drydock/object_caches/base.rb +24 -0
  32. data/lib/drydock/object_caches/filesystem_cache.rb +88 -0
  33. data/lib/drydock/object_caches/in_memory_cache.rb +52 -0
  34. data/lib/drydock/object_caches/no_cache.rb +38 -0
  35. data/lib/drydock/phase.rb +50 -0
  36. data/lib/drydock/phase_chain.rb +233 -0
  37. data/lib/drydock/plugins/apk.rb +31 -0
  38. data/lib/drydock/plugins/base.rb +15 -0
  39. data/lib/drydock/plugins/npm.rb +16 -0
  40. data/lib/drydock/plugins/package_manager.rb +30 -0
  41. data/lib/drydock/plugins/rubygems.rb +30 -0
  42. data/lib/drydock/project.rb +427 -0
  43. data/lib/drydock/runtime_options.rb +79 -0
  44. data/lib/drydock/stream_monitor.rb +54 -0
  45. data/lib/drydock/tar_writer.rb +36 -0
  46. data/lib/drydock.rb +35 -0
  47. data/spec/assets/MANIFEST +4 -0
  48. data/spec/assets/hello-world.txt +1 -0
  49. data/spec/assets/sample.tar +0 -0
  50. data/spec/assets/test.sh +3 -0
  51. data/spec/drydock/cli_flags_spec.rb +38 -0
  52. data/spec/drydock/container_config_spec.rb +230 -0
  53. data/spec/drydock/docker_api_patch_spec.rb +103 -0
  54. data/spec/drydock/drydock_spec.rb +25 -0
  55. data/spec/drydock/file_manager_spec.rb +53 -0
  56. data/spec/drydock/formatters_spec.rb +26 -0
  57. data/spec/drydock/ignorefile_definition_spec.rb +123 -0
  58. data/spec/drydock/image_repository_spec.rb +54 -0
  59. data/spec/drydock/object_caches/base_spec.rb +28 -0
  60. data/spec/drydock/object_caches/filesystem_cache_spec.rb +48 -0
  61. data/spec/drydock/object_caches/no_cache_spec.rb +62 -0
  62. data/spec/drydock/phase_chain_spec.rb +118 -0
  63. data/spec/drydock/phase_spec.rb +67 -0
  64. data/spec/drydock/plugins/apk_spec.rb +49 -0
  65. data/spec/drydock/plugins/base_spec.rb +13 -0
  66. data/spec/drydock/plugins/npm_spec.rb +26 -0
  67. data/spec/drydock/plugins/package_manager_spec.rb +12 -0
  68. data/spec/drydock/plugins/rubygems_spec.rb +53 -0
  69. data/spec/drydock/project_import_spec.rb +39 -0
  70. data/spec/drydock/project_spec.rb +156 -0
  71. data/spec/drydock/runtime_options_spec.rb +31 -0
  72. data/spec/drydock/stream_monitor_spec.rb +41 -0
  73. data/spec/drydock/tar_writer_spec.rb +27 -0
  74. data/spec/spec_helper.rb +47 -0
  75. data/spec/support/shared_examples/base_class.rb +3 -0
  76. data/spec/support/shared_examples/container_config.rb +12 -0
  77. data/spec/support/shared_examples/drydockfile.rb +6 -0
  78. 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