dockistrano 0.0.1
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/.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
|