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,62 @@
|
|
1
|
+
|
2
|
+
RSpec.describe Drydock::ObjectCaches::NoCache 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
|
+
blk = Proc.new { :value }
|
12
|
+
let(:cache) { described_class.new }
|
13
|
+
|
14
|
+
it 'always executes the block' do
|
15
|
+
expect(blk).to receive(:call) { :value }.exactly(3).times
|
16
|
+
expect { cache.fetch(:foo, &blk) }.not_to raise_error
|
17
|
+
expect(cache.fetch(:foo, &blk)).to eq(:value)
|
18
|
+
expect(cache.fetch(:foo, &blk)).to eq(:value)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#get' do
|
23
|
+
blk = Proc.new { :value }
|
24
|
+
let(:cache) { described_class.new }
|
25
|
+
|
26
|
+
it 'always returns nil' do
|
27
|
+
expect(blk).not_to receive(:call)
|
28
|
+
expect { cache.get(:foo) }.not_to raise_error
|
29
|
+
expect(cache.get(:foo)).to be_nil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#set' do
|
34
|
+
let(:cache) { described_class.new }
|
35
|
+
|
36
|
+
context 'when given a value' do
|
37
|
+
it 'returns nil' do
|
38
|
+
blk = Proc.new { |f| }
|
39
|
+
expect(blk).not_to receive(:call)
|
40
|
+
expect(cache.set(:foo, :some_value)).to be_nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'when given a writer block' do
|
45
|
+
it 'returns nil' do
|
46
|
+
blk = Proc.new { |f| }
|
47
|
+
expect(blk).to receive(:call).once
|
48
|
+
expect(cache.set(:foo, &blk)).to be_nil
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'invokes the block with a file' do
|
52
|
+
file = nil
|
53
|
+
blk = Proc.new { |f| }
|
54
|
+
expect(blk).to receive(:call) { |f| file = f }.once
|
55
|
+
|
56
|
+
cache.set(:foo, &blk)
|
57
|
+
expect(file).to be_an(IO)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
|
2
|
+
RSpec.describe Drydock::PhaseChain do
|
3
|
+
|
4
|
+
EXPECTED_IMAGE_TO_ID_MAPPINGS = {
|
5
|
+
'alpine' => {
|
6
|
+
'3.2' => 'f4fddc471ec2',
|
7
|
+
'latest' => 'f4fddc471ec2'
|
8
|
+
}
|
9
|
+
}
|
10
|
+
|
11
|
+
describe '.from_repo' do
|
12
|
+
|
13
|
+
it 'takes a repo name forced without a tag' do
|
14
|
+
expect { described_class.from_repo('alpine', nil) }.not_to raise_error
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'takes a repo name without a tag' do
|
18
|
+
expect { described_class.from_repo('alpine') }.not_to raise_error
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'takes a repo name and tag' do
|
22
|
+
expect { described_class.from_repo('alpine', 'latest') }.not_to raise_error
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#root_image' do
|
28
|
+
|
29
|
+
subject { described_class.from_repo('alpine', '3.2').root_image }
|
30
|
+
|
31
|
+
it 'returns the image object' do
|
32
|
+
expect(subject).to be_a(Docker::Image)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'matches the image ID' do
|
36
|
+
expect(subject.id).to eq(EXPECTED_IMAGE_TO_ID_MAPPINGS['alpine']['3.2'])
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#images' do
|
42
|
+
|
43
|
+
let(:chain) { described_class.from_repo('alpine', '3.2') }
|
44
|
+
subject { chain.images }
|
45
|
+
|
46
|
+
it 'returns only the root object' do
|
47
|
+
expect(subject).to eq([chain.root_image])
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '#last_image' do
|
53
|
+
|
54
|
+
let(:chain) { described_class.from_repo('alpine', '3.2') }
|
55
|
+
subject { chain.last_image }
|
56
|
+
it { is_expected.to be_nil }
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '#run' do
|
61
|
+
|
62
|
+
let(:chain) { described_class.from_repo('alpine', '3.2') }
|
63
|
+
|
64
|
+
it 'runs a simple command successfully' do
|
65
|
+
expect(chain.images.size).to eq(1)
|
66
|
+
expect { chain.run('/bin/hostname') }.not_to raise_error
|
67
|
+
expect(chain.images.size).to eq(2)
|
68
|
+
expect { chain.destroy! }.not_to raise_error
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'runs a command without committing successfully' do
|
72
|
+
expect(chain.images.size).to eq(1)
|
73
|
+
expect { chain.run('/bin/hostname', no_commit: true) }.not_to raise_error
|
74
|
+
expect(chain.images.size).to eq(1)
|
75
|
+
expect { chain.destroy! }.not_to raise_error
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'sets a comment or author when provided' do
|
79
|
+
expect { chain.run('/bin/hostname', comment: 'Some random comment', author: 'John Doe') }.not_to raise_error
|
80
|
+
chain.last_image.tap do |image|
|
81
|
+
image.refresh!
|
82
|
+
expect(image.info['Comment']).to eq('Some random comment')
|
83
|
+
expect(image.info['Author']).to eq('John Doe')
|
84
|
+
end
|
85
|
+
expect { chain.destroy! }.not_to raise_error
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'sets a command' do
|
89
|
+
expect { chain.run('/bin/hostname', command: ['/bin/ls', '/']) }.not_to raise_error
|
90
|
+
chain.last_image.tap do |image|
|
91
|
+
image.refresh!
|
92
|
+
expect(image.info['Config']['Cmd']).to eq(['/bin/ls', '/'])
|
93
|
+
end
|
94
|
+
expect { chain.destroy! }.not_to raise_error
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'sets an environment' do
|
98
|
+
expect { chain.run('/bin/hostname', env: ["APP_ROOT=/app", "BUILD_ROOT=/build"]) }.not_to raise_error
|
99
|
+
chain.last_image.tap do |image|
|
100
|
+
image.refresh!
|
101
|
+
expect(image.info['Config']['Env']).to include('APP_ROOT=/app')
|
102
|
+
expect(image.info['Config']['Env']).to include('BUILD_ROOT=/build')
|
103
|
+
end
|
104
|
+
expect { chain.destroy! }.not_to raise_error
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'exposes a port' do
|
108
|
+
expect { chain.run('/bin/hostname', expose: ['80/tcp']) }.not_to raise_error
|
109
|
+
chain.last_image.tap do |image|
|
110
|
+
image.refresh!
|
111
|
+
expect(image.info['Config']['ExposedPorts']).to eq("80/tcp" => {})
|
112
|
+
end
|
113
|
+
expect { chain.destroy! }.not_to raise_error
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
|
2
|
+
RSpec.describe Drydock::Phase do
|
3
|
+
|
4
|
+
describe '.from' do
|
5
|
+
|
6
|
+
it 'takes optional values' do
|
7
|
+
expect { described_class.from({}) }.not_to raise_error
|
8
|
+
end
|
9
|
+
|
10
|
+
[:source_image, :build_container, :result_image].each do |name|
|
11
|
+
it "takes a valid #{name} attribute" do
|
12
|
+
expect { described_class.from(name => 'value_x') }.not_to raise_error
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'rejects an invalid attribute' do
|
17
|
+
expect { described_class.from(whatever: 'value_x') }.to raise_error(ArgumentError)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
let(:build_container) { double('BuildContainer') }
|
23
|
+
let(:no_build_container) { described_class.from(source_image: '123', result_image: '456') }
|
24
|
+
let(:has_build_container) { described_class.from(source_image: '123', result_image: '456', build_container: build_container) }
|
25
|
+
|
26
|
+
describe '#cached?' do
|
27
|
+
|
28
|
+
context 'when a build container is missing' do
|
29
|
+
subject { no_build_container.cached? }
|
30
|
+
it { is_expected.to be_truthy }
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'when a build container is present' do
|
34
|
+
subject { has_build_container.cached? }
|
35
|
+
it { is_expected.to be_falsey }
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#built?' do
|
41
|
+
|
42
|
+
context 'when a build container is missing' do
|
43
|
+
subject { no_build_container.built? }
|
44
|
+
it { is_expected.to be_falsey }
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'when a build container is present' do
|
48
|
+
subject { has_build_container.built? }
|
49
|
+
it { is_expected.to be_truthy }
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
describe 'finalize!' do
|
55
|
+
|
56
|
+
it 'when a build container is missing' do
|
57
|
+
expect { no_build_container.finalize! }.not_to raise_error
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'when a build container is present' do
|
61
|
+
expect(build_container).to receive(:remove).with(hash_including(:force))
|
62
|
+
expect { has_build_container.finalize! }.not_to raise_error
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
|
2
|
+
RSpec.describe Drydock::Plugins::APK do
|
3
|
+
|
4
|
+
let(:project) { double('Project') }
|
5
|
+
let(:plugin) { described_class.new(project) }
|
6
|
+
|
7
|
+
describe '#add' do
|
8
|
+
|
9
|
+
it 'adds one package' do
|
10
|
+
expect(project).to receive(:run).with('apk add ruby', {})
|
11
|
+
expect { plugin.add('ruby') }.not_to raise_error
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'adds more than one packages' do
|
15
|
+
expect(project).to receive(:run).with('apk add curl ruby nodejs', {})
|
16
|
+
expect { plugin.add('curl', 'ruby', 'nodejs') }.not_to raise_error
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#remove' do
|
22
|
+
|
23
|
+
it 'deletes one package' do
|
24
|
+
expect(project).to receive(:run).with('apk del ruby')
|
25
|
+
expect { plugin.remove('ruby') }.not_to raise_error
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'deletes more than one packages' do
|
29
|
+
expect(project).to receive(:run).with('apk del curl python nginx')
|
30
|
+
expect { plugin.remove('curl', 'python', 'nginx') }.not_to raise_error
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '#update' do
|
36
|
+
it 'updates the package index' do
|
37
|
+
expect(project).to receive(:run).with('apk update')
|
38
|
+
expect { plugin.update }.not_to raise_error
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#upgrade' do
|
43
|
+
it 'upgrades the installed packages' do
|
44
|
+
expect(project).to receive(:run).with('apk upgrade')
|
45
|
+
expect { plugin.upgrade }.not_to raise_error
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
RSpec.describe Drydock::Plugins::NPM do
|
3
|
+
|
4
|
+
let(:project) { double('Project') }
|
5
|
+
let(:plugin) { described_class.new(project) }
|
6
|
+
|
7
|
+
describe '#install' do
|
8
|
+
|
9
|
+
it 'adds one package' do
|
10
|
+
expect(project).to receive(:run).with('npm install sinopia')
|
11
|
+
expect { plugin.install('sinopia') }.not_to raise_error
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'adds one package globally' do
|
15
|
+
expect(project).to receive(:run).with('npm install -g bower')
|
16
|
+
expect { plugin.install('bower', g: true) }.not_to raise_error
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'adds more than one packages' do
|
20
|
+
expect(project).to receive(:run).with('npm install sinopia bower')
|
21
|
+
expect { plugin.install('sinopia', 'bower') }.not_to raise_error
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
|
2
|
+
RSpec.describe Drydock::Plugins::PackageManager do
|
3
|
+
|
4
|
+
[:add, :clean, :remove, :update, :upgrade].each do |action|
|
5
|
+
describe "##{action}" do
|
6
|
+
it 'is not implemented' do
|
7
|
+
expect { described_class.new(nil).public_send(action) }.to raise_error(NotImplementedError)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
|
2
|
+
RSpec.describe Drydock::Plugins::Rubygems do
|
3
|
+
|
4
|
+
let(:project) { double('Project') }
|
5
|
+
let(:plugin) { described_class.new(project) }
|
6
|
+
|
7
|
+
describe '#add_source' do
|
8
|
+
|
9
|
+
it 'adds one source' do
|
10
|
+
expect(project).to receive(:run).with('gem sources --add http://whatever/')
|
11
|
+
expect { plugin.add_source('http://whatever/') }.not_to raise_error
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#remove_source' do
|
17
|
+
|
18
|
+
it 'removes one source' do
|
19
|
+
expect(project).to receive(:run).with('gem sources --remove http://whatever/')
|
20
|
+
expect { plugin.remove_source('http://whatever/') }.not_to raise_error
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#install' do
|
26
|
+
|
27
|
+
it 'installs one package' do
|
28
|
+
expect(project).to receive(:run).with('gem install curb ', timeout: 120)
|
29
|
+
expect { plugin.install('curb') }.not_to raise_error
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'installs one package with timeout' do
|
33
|
+
expect(project).to receive(:run).with('gem install curb ', timeout: 45)
|
34
|
+
expect { plugin.install('curb', timeout: 45) }.not_to raise_error
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'installs one package without docs' do
|
38
|
+
expect(project).to receive(:run).with('gem install curb --no-rdoc --no-ri ', timeout: 120)
|
39
|
+
expect { plugin.install('curb', rdoc: false, ri: false) }.not_to raise_error
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
describe '#update_system' do
|
45
|
+
|
46
|
+
it 'updates rubygems' do
|
47
|
+
expect(project).to receive(:run).with('gem update --system ', timeout: 200)
|
48
|
+
expect { plugin.update_system(timeout: 200) }.not_to raise_error
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
|
2
|
+
RSpec.describe Drydock::Project do
|
3
|
+
|
4
|
+
let(:project) { described_class.new }
|
5
|
+
after(:each) { project.destroy!(force: true) if project }
|
6
|
+
|
7
|
+
it 'derives a project and imports between derivations' do
|
8
|
+
project.from('alpine')
|
9
|
+
project.mkdir('/app/1')
|
10
|
+
|
11
|
+
build = project.derive
|
12
|
+
build.mkdir('/app/2')
|
13
|
+
build.run('echo 427911 > /app/1/VERSION')
|
14
|
+
build.run('echo 427912 > /app/2/VERSION')
|
15
|
+
build.run('echo 427913 > /tmp/VERSION')
|
16
|
+
|
17
|
+
app = project.derive
|
18
|
+
app.import('/app', from: build)
|
19
|
+
|
20
|
+
b_container = build.last_image.run('cat /tmp/VERSION')
|
21
|
+
b_output = b_container.tap(&:wait).streaming_logs(stdout: true, stderr: true)
|
22
|
+
b_container.remove
|
23
|
+
|
24
|
+
expect(b_output).to eq("427913\n")
|
25
|
+
|
26
|
+
v1_container = app.last_image.run('cat /app/2/VERSION')
|
27
|
+
v1_output = v1_container.tap(&:wait).streaming_logs(stdout: true, stderr: true)
|
28
|
+
v1_container.remove
|
29
|
+
|
30
|
+
expect(v1_output).to eq("427912\n")
|
31
|
+
|
32
|
+
v2_container = app.last_image.run('cat /tmp/VERSION')
|
33
|
+
v2_output = v2_container.tap(&:wait).streaming_logs(stdout: true, stderr: true)
|
34
|
+
v2_container.remove
|
35
|
+
|
36
|
+
expect(v2_output).to include("'/tmp/VERSION': No such file or directory")
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
|
2
|
+
RSpec.describe Drydock::Project do
|
3
|
+
|
4
|
+
let(:project) { described_class.new }
|
5
|
+
after(:each) { project.destroy!(force: true) if project }
|
6
|
+
|
7
|
+
let(:asset_path) { 'spec/assets' }
|
8
|
+
|
9
|
+
it 'require a `from` before a `run`' do
|
10
|
+
expect { project.run('ls /') }.to raise_error(Drydock::InvalidInstructionError)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'require only one `from` per project' do
|
14
|
+
expect { project.from('alpine') }.not_to raise_error
|
15
|
+
expect { project.from('alpine') }.to raise_error(Drydock::InvalidInstructionError)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'sets no extra information on image commit' do
|
19
|
+
expect { project.from('alpine') }.not_to raise_error
|
20
|
+
expect { project.run('ls /') }.not_to raise_error
|
21
|
+
|
22
|
+
image = Docker::Image.get(project.last_image.id)
|
23
|
+
expect(image).not_to be_nil
|
24
|
+
expect(image.info['Author']).to eq('')
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'sets the author after an image commit' do
|
28
|
+
name = 'John Doe'
|
29
|
+
email = 'john@doe.int'
|
30
|
+
author = 'John Doe <john@doe.int>'
|
31
|
+
|
32
|
+
project.from('alpine')
|
33
|
+
project.author(name: name, email: email)
|
34
|
+
|
35
|
+
expect { project.run('ls /') }.not_to raise_error
|
36
|
+
expect(project.last_image).not_to be_nil
|
37
|
+
|
38
|
+
image = Docker::Image.get(project.last_image.id)
|
39
|
+
expect(image).not_to be_nil
|
40
|
+
expect(image.info['Author']).to eq(author)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'build ID is incremented at every build step' do
|
44
|
+
expect(project.build_id).to eq('0')
|
45
|
+
|
46
|
+
project.from('alpine')
|
47
|
+
expect(project.build_id).to eq('1')
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'copies asset files into an image' do
|
51
|
+
project.from('alpine')
|
52
|
+
expect { project.copy(asset_path, '/', chmod: false, no_cache: true, recursive: true) }.not_to raise_error
|
53
|
+
|
54
|
+
expect(project.last_image).not_to be_nil
|
55
|
+
expect(project.last_image.id).not_to be_empty
|
56
|
+
|
57
|
+
hash_container = project.last_image.run('sha1sum /spec/assets/hello-world.txt')
|
58
|
+
hash_output = hash_container.tap(&:wait).streaming_logs(stdout: true, stderr: true)
|
59
|
+
hash_container.remove
|
60
|
+
|
61
|
+
expect(hash_output).to include('60fde9c2310b0d4cad4dab8d126b04387efba289')
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'autocreates the target path on copy' do
|
65
|
+
project.from('alpine')
|
66
|
+
expect {
|
67
|
+
project.copy(asset_path, '/assets', chmod: false, no_cache: true, recursive: true)
|
68
|
+
}.to raise_error(Drydock::InvalidInstructionError)
|
69
|
+
|
70
|
+
expect(project.last_image).to be_nil
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'downloads from the source URL once' do
|
74
|
+
expect(Excon).to receive(:get).once.with('http://httpbin.org/ip', hash_including(:response_block))
|
75
|
+
|
76
|
+
project.set :cache, Drydock::ObjectCaches::InMemoryCache.new
|
77
|
+
project.from('alpine')
|
78
|
+
expect {
|
79
|
+
project.download_once('http://httpbin.org/ip', '/etc/ip_address.json', chmod: 0600)
|
80
|
+
project.download_once('http://httpbin.org/ip', '/etc/ip_address_2.json', chmod: 0600)
|
81
|
+
}.not_to raise_error
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'sets the raw Cmd' do
|
85
|
+
project.from('alpine')
|
86
|
+
project.cmd(['/bin/bash'])
|
87
|
+
|
88
|
+
expect(project.last_image).not_to be_nil
|
89
|
+
|
90
|
+
image = Docker::Image.get(project.last_image.id)
|
91
|
+
expect(image).not_to be_nil
|
92
|
+
expect(image.info['Config']['Cmd']).to eq(['/bin/bash'])
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'sets the shell Cmd' do
|
96
|
+
project.from('alpine')
|
97
|
+
project.cmd('/bin/ls')
|
98
|
+
|
99
|
+
expect(project.last_image).not_to be_nil
|
100
|
+
|
101
|
+
image = Docker::Image.get(project.last_image.id)
|
102
|
+
expect(image).not_to be_nil
|
103
|
+
expect(image.info['Config']['Cmd']).to eq(['/bin/sh', '-c', '/bin/ls'])
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'keeps the shell Cmd even after another build command' do
|
107
|
+
project.from('alpine')
|
108
|
+
project.cmd('/bin/ls')
|
109
|
+
project.run('/bin/date')
|
110
|
+
|
111
|
+
expect(project.last_image).not_to be_nil
|
112
|
+
|
113
|
+
image = Docker::Image.get(project.last_image.id)
|
114
|
+
expect(image).not_to be_nil
|
115
|
+
expect(image.info['Config']['Cmd']).to eq(['/bin/sh', '-c', '/bin/ls'])
|
116
|
+
expect(image.info['ContainerConfig']['Cmd']).to eq(['/bin/sh', '-c', '/bin/date'])
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'sets the Env' do
|
120
|
+
project.from('alpine')
|
121
|
+
project.env('APP_ROOT_TEST', '/app/current')
|
122
|
+
|
123
|
+
expect(project.last_image).not_to be_nil
|
124
|
+
|
125
|
+
image = Docker::Image.get(project.last_image.id)
|
126
|
+
expect(image).not_to be_nil
|
127
|
+
expect(image.info['Config']['Env']).to include('APP_ROOT_TEST=/app/current')
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'sets the Env with multiple values' do
|
131
|
+
project.from('alpine')
|
132
|
+
project.env('APP_ROOT_TEST', '/app/current')
|
133
|
+
project.env('BUILD_ROOT', '/tmp/build')
|
134
|
+
|
135
|
+
expect(project.last_image).not_to be_nil
|
136
|
+
|
137
|
+
image = Docker::Image.get(project.last_image.id)
|
138
|
+
expect(image).not_to be_nil
|
139
|
+
expect(image.info['Config']['Env']).to include('APP_ROOT_TEST=/app/current')
|
140
|
+
expect(image.info['Config']['Env']).to include('BUILD_ROOT=/tmp/build')
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'sets the ExposedPorts' do
|
144
|
+
project.from('alpine')
|
145
|
+
project.expose(tcp: [80, 443], udp: 53)
|
146
|
+
|
147
|
+
expect(project.last_image).not_to be_nil
|
148
|
+
|
149
|
+
image = Docker::Image.get(project.last_image.id)
|
150
|
+
expect(image).not_to be_nil
|
151
|
+
expect(image.info['Config']['ExposedPorts']).to include('80/tcp')
|
152
|
+
expect(image.info['Config']['ExposedPorts']).to include('443/tcp')
|
153
|
+
expect(image.info['Config']['ExposedPorts']).to include('53/udp')
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
RSpec.describe Drydock::RuntimeOptions do
|
3
|
+
|
4
|
+
describe '.parse!' do
|
5
|
+
it 'accepts build options' do
|
6
|
+
opts = described_class.parse!(%w{--no-cache -i test.rb -q})
|
7
|
+
expect(opts.cache).to be_falsey
|
8
|
+
expect(opts.includes).to include('test.rb')
|
9
|
+
expect(opts.log_level).to eq(Logger::ERROR)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'accepts extra build arguments' do
|
13
|
+
opts = described_class.parse!(%w{--build-opts version=2.0.3 --build-opts validate=true})
|
14
|
+
|
15
|
+
expect(opts.build_opts['version']).to eq('2.0.3')
|
16
|
+
expect(opts.build_opts[:version]).to eq('2.0.3')
|
17
|
+
expect(opts.build_opts[:validate]).to eq('true')
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'converts timeout to integer' do
|
21
|
+
opts = described_class.parse!(%w{--timeout 20})
|
22
|
+
expect(opts.read_timeout).to eq(20)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'allows one to enable debug mode' do
|
26
|
+
opts = described_class.parse!(%w{--verbose})
|
27
|
+
expect(opts.log_level).to eq(Logger::DEBUG)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
|
2
|
+
RSpec.describe Drydock::StreamMonitor do
|
3
|
+
let(:events) { Array.new }
|
4
|
+
let(:handler) { lambda { |evt, is_new, serial, evt_type| events << evt } }
|
5
|
+
let(:monitor) { described_class.new(handler) }
|
6
|
+
|
7
|
+
after(:each) do
|
8
|
+
monitor.kill
|
9
|
+
monitor.join
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#alive?' do
|
13
|
+
subject { monitor.alive? }
|
14
|
+
it { is_expected.to be_truthy }
|
15
|
+
end
|
16
|
+
|
17
|
+
describe 'scenario' do
|
18
|
+
let(:image) { Docker::Image.create(fromImage: 'alpine', tag: 'latest') }
|
19
|
+
let(:container) { image.run('ls -l').tap(&:wait) }
|
20
|
+
let(:run_image) { container.commit }
|
21
|
+
|
22
|
+
after(:each) do
|
23
|
+
container.remove
|
24
|
+
run_image.remove
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'receives the precise number of events' do
|
28
|
+
expect(events).to have(0).items
|
29
|
+
|
30
|
+
expect(monitor).not_to be_nil
|
31
|
+
expect(run_image).not_to be_nil
|
32
|
+
|
33
|
+
expect(events).to have(5).items
|
34
|
+
|
35
|
+
commit_event = events.find { |evt| evt.status == 'commit' }
|
36
|
+
expect(commit_event).not_to be_nil
|
37
|
+
expect(commit_event.id).to eq(container.id)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|