dockistrano 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/Guardfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +101 -0
- data/Rakefile +1 -0
- data/bin/doc +13 -0
- data/bin/docker +0 -0
- data/dockistrano.gemspec +32 -0
- data/lib/dockistrano/cli.rb +220 -0
- data/lib/dockistrano/command_line.rb +28 -0
- data/lib/dockistrano/docker.rb +188 -0
- data/lib/dockistrano/git.rb +27 -0
- data/lib/dockistrano/hipache.rb +62 -0
- data/lib/dockistrano/registry.rb +48 -0
- data/lib/dockistrano/service.rb +331 -0
- data/lib/dockistrano/service_dependency.rb +114 -0
- data/lib/dockistrano/version.rb +3 -0
- data/lib/dockistrano.rb +14 -0
- data/spec/dockistrano/cli_spec.rb +296 -0
- data/spec/dockistrano/command_line_spec.rb +27 -0
- data/spec/dockistrano/docker_spec.rb +242 -0
- data/spec/dockistrano/git_spec.rb +48 -0
- data/spec/dockistrano/hipache_spec.rb +81 -0
- data/spec/dockistrano/registry_spec.rb +56 -0
- data/spec/dockistrano/service_dependency_spec.rb +154 -0
- data/spec/dockistrano/service_spec.rb +536 -0
- data/spec/fixtures/project_1/Dockerfile +0 -0
- data/spec/fixtures/project_1/config/dockistrano.yml +8 -0
- data/spec/spec_helper.rb +21 -0
- metadata +242 -0
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Dockistrano::Hipache do
|
4
|
+
|
5
|
+
subject { described_class.new("127.0.0.1") }
|
6
|
+
let(:redis) { double.as_null_object }
|
7
|
+
|
8
|
+
before do
|
9
|
+
allow(subject).to receive(:redis).and_return(redis)
|
10
|
+
end
|
11
|
+
|
12
|
+
context "#online?" do
|
13
|
+
it "returns true when Hipache is online" do
|
14
|
+
allow(redis).to receive(:ping).and_return(true)
|
15
|
+
expect(subject.online?).to be_true
|
16
|
+
end
|
17
|
+
|
18
|
+
it "returns false when Hipache is offline" do
|
19
|
+
allow(redis).to receive(:ping).and_raise(Redis::CannotConnectError)
|
20
|
+
expect(subject.online?).to be_false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "#wait_for_online" do
|
25
|
+
it "waits until Hipache is online" do
|
26
|
+
expect(subject).to receive(:online?).and_return(false, false, false, true)
|
27
|
+
expect(Kernel).to receive(:sleep).exactly(3).times
|
28
|
+
subject.wait_for_online
|
29
|
+
end
|
30
|
+
|
31
|
+
it "waits for a maximum of 5 seconds" do
|
32
|
+
expect(subject).to receive(:online?).and_return(false, false, false, false, false, false)
|
33
|
+
expect(Kernel).to receive(:sleep).exactly(5).times
|
34
|
+
subject.wait_for_online
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context "#register" do
|
39
|
+
before do
|
40
|
+
allow(subject).to receive(:wait_for_online)
|
41
|
+
allow(subject).to receive(:online?).and_return(true)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "waits for Hipache to be online" do
|
45
|
+
expect(subject).to receive(:wait_for_online)
|
46
|
+
subject.register("foobar", "application.dev", "33.33.33.33", "80")
|
47
|
+
end
|
48
|
+
|
49
|
+
it "removes any previously used addresses for the host" do
|
50
|
+
expect(redis).to receive(:lrange).with("frontend:application.dev", 0, -1).and_return(["33.33.33.33:80"])
|
51
|
+
expect(redis).to receive(:del).with("frontend:application.dev")
|
52
|
+
subject.register("foobar", "application.dev", "33.33.33.33", "80")
|
53
|
+
end
|
54
|
+
|
55
|
+
it "creates a new host in Hipache" do
|
56
|
+
expect(redis).to receive(:rpush).with("frontend:application.dev", "foobar")
|
57
|
+
expect(redis).to receive(:rpush).with("frontend:application.dev", "http://33.33.33.33:80")
|
58
|
+
subject.register("foobar", "application.dev", "33.33.33.33", "80")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context "#unregister" do
|
63
|
+
it "removes the ip address from Hipache" do
|
64
|
+
expect(subject).to receive(:online?).and_return(true)
|
65
|
+
expect(redis).to receive(:lrem).with("frontend:application.dev", 0, "http://33.33.33.33:80")
|
66
|
+
subject.unregister("foobar", "application.dev", "33.33.33.33", "80")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "#status" do
|
71
|
+
it "returns a hash with registered hosts in Hipache" do
|
72
|
+
allow(subject).to receive(:online?).and_return(true)
|
73
|
+
expect(redis).to receive(:keys).with("frontend:*").and_return(["frontend:application.dev"])
|
74
|
+
expect(redis).to receive(:lrange).with("frontend:application.dev", 1, -1).and_return(["http://33.33.33.33:80"])
|
75
|
+
expect(subject.status).to eq({
|
76
|
+
"application.dev" => ["http://33.33.33.33:80"]
|
77
|
+
})
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Dockistrano::Registry do
|
4
|
+
|
5
|
+
subject { described_class.new("registry.dev") }
|
6
|
+
|
7
|
+
context "#tags_for_image" do
|
8
|
+
it "returns all available tags on the registry" do
|
9
|
+
stub_request(:get, "http://registry.dev/v1/repositories/image_name/tags").to_return({
|
10
|
+
status: 200,
|
11
|
+
body: '{"develop": "49f387cc90f2d5b82ded91c239b6e583f8b955cb532912cc959b1d1289b3f8f1"}'
|
12
|
+
})
|
13
|
+
|
14
|
+
expect(subject.tags_for_image("image_name")).to eq({
|
15
|
+
"develop" => "49f387cc90f2d5b82ded91c239b6e583f8b955cb532912cc959b1d1289b3f8f1"
|
16
|
+
})
|
17
|
+
end
|
18
|
+
|
19
|
+
it "raises an error when the repository is not found in the registry" do
|
20
|
+
stub_request(:get, "http://registry.dev/v1/repositories/foobar/tags").to_return({
|
21
|
+
status: 404,
|
22
|
+
body: '{"error": "Repository not found"}'
|
23
|
+
})
|
24
|
+
expect { subject.tags_for_image("foobar") }.to raise_error(Dockistrano::Registry::RepositoryNotFoundInRegistry)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "raises an error when the request failed" do
|
28
|
+
stub_request(:get, "http://registry.dev/v1/repositories/foobar/tags").to_return({
|
29
|
+
status: 500,
|
30
|
+
body: '{"error": "Something else"}'
|
31
|
+
})
|
32
|
+
expect { subject.tags_for_image("foobar") }.to raise_error("Something else")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "#latest_id_for_image" do
|
37
|
+
it "returns the id for the image in the registry" do
|
38
|
+
stub_request(:get, "http://registry.dev/v1/repositories/foobar/tags/develop").to_return({
|
39
|
+
status: 200,
|
40
|
+
body: '"49f387cc90f2d5b82ded91c239b6e583f8b955cb532912cc959b1d1289b3f8f1"'
|
41
|
+
})
|
42
|
+
|
43
|
+
expect(subject.latest_id_for_image("foobar", "develop")).to eq("49f387cc90f2d5b82ded91c239b6e583f8b955cb532912cc959b1d1289b3f8f1")
|
44
|
+
end
|
45
|
+
|
46
|
+
it "returns nil when the image is not available in the registry" do
|
47
|
+
stub_request(:get, "http://registry.dev/v1/repositories/foobar/tags/develop").to_return({
|
48
|
+
status: 404,
|
49
|
+
body: '{"error": "Tag not found"}'
|
50
|
+
})
|
51
|
+
|
52
|
+
expect(subject.latest_id_for_image("foobar", "develop")).to be_nil
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Dockistrano::ServiceDependency do
|
4
|
+
|
5
|
+
subject { described_class.new(service, "postgresql", { database: "application_development" }) }
|
6
|
+
let(:service) { double(tag: "develop", registry: "my.registry.net") }
|
7
|
+
let(:service_dependency) { double }
|
8
|
+
|
9
|
+
context ".factory" do
|
10
|
+
it "creates a new service based on the name" do
|
11
|
+
expect(described_class).to receive(:new).and_return(service_dependency)
|
12
|
+
expect(service_dependency).to receive(:backing_service).and_return(service)
|
13
|
+
expect(described_class.factory(service, "redis", { foo: "bar" })).to eq(service)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "#initialize" do
|
18
|
+
before do
|
19
|
+
allow(subject).to receive(:load_config).and_return({ "default" => {} })
|
20
|
+
allow(subject).to receive(:tag_with_fallback).and_return("develop")
|
21
|
+
end
|
22
|
+
|
23
|
+
it "backing service has registry of the service" do
|
24
|
+
expect(subject.backing_service.registry).to eq("my.registry.net")
|
25
|
+
end
|
26
|
+
|
27
|
+
it "backing service has the name of the dependency" do
|
28
|
+
expect(subject.backing_service.image_name).to eq("postgresql")
|
29
|
+
end
|
30
|
+
|
31
|
+
it "backing service has the tag of the service" do
|
32
|
+
expect(subject.backing_service.tag).to eq("develop")
|
33
|
+
end
|
34
|
+
|
35
|
+
it "backing service has backing service environment variables from configuration" do
|
36
|
+
expect(subject.backing_service.backing_service_env).to eq({ database: "application_development" })
|
37
|
+
end
|
38
|
+
|
39
|
+
it "sets the tag with a fallback" do
|
40
|
+
expect(subject).to receive(:tag_with_fallback).with("develop").and_return("latest")
|
41
|
+
expect(subject.backing_service.tag).to eq("latest")
|
42
|
+
end
|
43
|
+
|
44
|
+
it "loads the configuration" do
|
45
|
+
expect(subject).to receive(:load_config).and_return({ "default" => { "test_command" => "foobar" }})
|
46
|
+
expect(subject.backing_service.test_command).to eq("foobar")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "#load_config" do
|
51
|
+
it "uses the cache when available" do
|
52
|
+
configuration = double
|
53
|
+
allow(subject).to receive(:load_from_cache).and_return(configuration)
|
54
|
+
expect(subject.load_config).to eq(configuration)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "loads configuration from the image when no cache is available" do
|
58
|
+
configuration = double
|
59
|
+
allow(subject).to receive(:load_from_cache).and_return(nil)
|
60
|
+
allow(subject).to receive(:load_from_image).and_return(configuration)
|
61
|
+
expect(subject.load_config).to eq(configuration)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context "#load_from_cache" do
|
66
|
+
let(:backing_service) { double(image_id: "123456789") }
|
67
|
+
|
68
|
+
before do
|
69
|
+
allow(subject).to receive(:backing_service).and_return(backing_service)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "returns nil when no cache is found" do
|
73
|
+
expect(File).to receive(:exists?).with("tmp/configuration_cache/123456789").and_return(false)
|
74
|
+
expect(subject.load_from_cache).to eq(nil)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "returns the configuration when a cache is found" do
|
78
|
+
expect(File).to receive(:exists?).with("tmp/configuration_cache/123456789").and_return(true)
|
79
|
+
expect(YAML).to receive(:load_file).with("tmp/configuration_cache/123456789").and_return({ "default" => { "image_name" => "foobar "}})
|
80
|
+
expect(subject.load_from_cache).to eq({ "default" => { "image_name" => "foobar "}})
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context "#load_from_image" do
|
85
|
+
let(:backing_service) { double(full_image_name: "registry/application:develop", image_id: "123456789") }
|
86
|
+
let(:configuration) { double }
|
87
|
+
|
88
|
+
before do
|
89
|
+
allow(subject).to receive(:backing_service).and_return(backing_service)
|
90
|
+
end
|
91
|
+
|
92
|
+
it "reads the configuration from the image and caches the configuration" do
|
93
|
+
expect(Dockistrano::Docker).to receive(:run).with(backing_service.full_image_name, command: "cat /dockistrano.yml").and_return(raw_config = "---\ndefault:\n\tconfiguration: value")
|
94
|
+
|
95
|
+
expect(FileUtils).to receive(:mkdir_p).with("tmp/configuration_cache")
|
96
|
+
expect(File).to receive(:open).with("tmp/configuration_cache/#{backing_service.image_id}", "w+").and_return(file = double)
|
97
|
+
expect(file).to receive(:write).with(raw_config)
|
98
|
+
expect(file).to receive(:close)
|
99
|
+
|
100
|
+
expect(YAML).to receive(:load).with(raw_config).and_return(configuration)
|
101
|
+
|
102
|
+
expect(subject.load_from_image).to eq(configuration)
|
103
|
+
end
|
104
|
+
|
105
|
+
it "raises an error when host directories are missing" do
|
106
|
+
expect(Dockistrano::Docker).to receive(:run).with(backing_service.full_image_name, command: "cat /dockistrano.yml").and_return("No such file or directory: failed to mount")
|
107
|
+
expect { subject.load_from_image }.to raise_error(Dockistrano::ServiceDependency::HostDirectoriesMissing)
|
108
|
+
end
|
109
|
+
|
110
|
+
it "raises an error when the configuration is not found" do
|
111
|
+
expect(Dockistrano::Docker).to receive(:run).with(backing_service.full_image_name, command: "cat /dockistrano.yml").and_return("No such file or directory: dockistrano.yml")
|
112
|
+
expect { subject.load_from_image }.to raise_error(Dockistrano::ServiceDependency::ContainerConfigurationMissing)
|
113
|
+
end
|
114
|
+
|
115
|
+
it "raises an error when the configuration is empty" do
|
116
|
+
expect(Dockistrano::Docker).to receive(:run).with(backing_service.full_image_name, command: "cat /dockistrano.yml").and_return("")
|
117
|
+
expect { subject.load_from_image }.to raise_error(Dockistrano::ServiceDependency::ContainerConfigurationMissing)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
context "#tag_with_fallback" do
|
122
|
+
let(:backing_service) { double(registry: "registry", image_name: "postgresql") }
|
123
|
+
|
124
|
+
before do
|
125
|
+
allow(subject).to receive(:backing_service).and_return(backing_service)
|
126
|
+
end
|
127
|
+
|
128
|
+
it "returns the given tag when the tag is available" do
|
129
|
+
expect(Dockistrano::Docker).to receive(:tags_for_image).and_return(["feature-branch", "develop", "master", "latest"])
|
130
|
+
expect(subject.tag_with_fallback("feature-branch")).to eq("feature-branch")
|
131
|
+
end
|
132
|
+
|
133
|
+
it "returns develop when the specific tag is not available" do
|
134
|
+
expect(Dockistrano::Docker).to receive(:tags_for_image).and_return(["develop", "master", "latest"])
|
135
|
+
expect(subject.tag_with_fallback("feature-branch")).to eq("develop")
|
136
|
+
end
|
137
|
+
|
138
|
+
it "returns master when develop is not available" do
|
139
|
+
expect(Dockistrano::Docker).to receive(:tags_for_image).and_return(["master", "latest"])
|
140
|
+
expect(subject.tag_with_fallback("feature-branch")).to eq("master")
|
141
|
+
end
|
142
|
+
|
143
|
+
it "returns latest when master is not available" do
|
144
|
+
expect(Dockistrano::Docker).to receive(:tags_for_image).and_return(["latest"])
|
145
|
+
expect(subject.tag_with_fallback("feature-branch")).to eq("latest")
|
146
|
+
end
|
147
|
+
|
148
|
+
it "raises an error when not appropriate tag is found" do
|
149
|
+
expect(Dockistrano::Docker).to receive(:tags_for_image).and_return(["another-feature-branch"])
|
150
|
+
expect { subject.tag_with_fallback("feature-branch") }.to raise_error(Dockistrano::ServiceDependency::NoTagFoundForImage)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|