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.
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