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