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