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
         |