servitor 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +105 -0
- data/LICENSE +19 -0
- data/README.md +102 -0
- data/lib/cli/cli.rb +96 -0
- data/lib/configuration/configuration.rb +2 -0
- data/lib/configuration/configuration_resolver.rb +97 -0
- data/lib/configuration/yaml_configuration_provider.rb +28 -0
- data/lib/control/control.rb +0 -0
- data/lib/deployment/bundle_deployer.rb +14 -0
- data/lib/deployment/deployment.rb +2 -0
- data/lib/deployment/envdir_deployer.rb +34 -0
- data/lib/helpers/child_process_helper.rb +41 -0
- data/lib/helpers/helpers.rb +1 -0
- data/lib/infrastructure/infrastructure.rb +16 -0
- data/lib/infrastructure/infrastructure_provisioner.rb +25 -0
- data/lib/infrastructure/infrastructure_requirements.rb +25 -0
- data/lib/provisioners/Vagrantfile.erb +16 -0
- data/lib/provisioners/provisioners.rb +6 -0
- data/lib/provisioners/ssh_key.rb +22 -0
- data/lib/provisioners/vagrant_box.rb +116 -0
- data/lib/provisioners/vagrant_box_provisioner.rb +37 -0
- data/lib/provisioners/vagrant_box_ruby_installer.rb +34 -0
- data/lib/provisioners/vagrantfile.rb +34 -0
- data/lib/provisioners/veewee_box.rb +91 -0
- data/lib/resource/mysql_resource.rb +33 -0
- data/lib/resource/resource.rb +1 -0
- data/lib/service/service.rb +12 -0
- data/lib/service/service_definition/Servicefile.erb +54 -0
- data/lib/service/service_definition/deployment_stages.rb +16 -0
- data/lib/service/service_definition/resource_configuration.rb +14 -0
- data/lib/service/service_definition/service_configuration.rb +19 -0
- data/lib/service/service_definition/service_definition.rb +49 -0
- data/lib/service/service_definition/variable.rb +22 -0
- data/lib/service/service_file_parser.rb +21 -0
- data/lib/service/service_graph/service_graph.rb +58 -0
- data/lib/service/service_graph/service_graph_flattener.rb +34 -0
- data/lib/service/service_graph/service_graph_node.rb +15 -0
- data/lib/service/service_linker.rb +56 -0
- data/lib/service/service_locator.rb +43 -0
- data/lib/servitor.rb +47 -0
- data/servitor.gemspec +18 -0
- data/spec/provisioners/vagrant_basebox_provisioner_spec.rb +63 -0
- data/spec/provisioners/vagrant_box_ruby_provisioner_spec.rb +48 -0
- data/spec/spec_helper.rb +7 -0
- metadata +171 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*.erb.cache
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
CFPropertyList (2.0.17)
|
5
|
+
libxml-ruby (>= 1.1.0)
|
6
|
+
rake (>= 0.7.0)
|
7
|
+
Platform (0.4.0)
|
8
|
+
ansi (1.3.0)
|
9
|
+
archive-tar-minitar (0.5.2)
|
10
|
+
builder (3.1.3)
|
11
|
+
childprocess (0.3.5)
|
12
|
+
ffi (~> 1.0, >= 1.0.6)
|
13
|
+
cucumber (1.2.1)
|
14
|
+
builder (>= 2.1.2)
|
15
|
+
diff-lcs (>= 1.1.3)
|
16
|
+
gherkin (~> 2.11.0)
|
17
|
+
json (>= 1.4.6)
|
18
|
+
diff-lcs (1.1.3)
|
19
|
+
erubis (2.7.0)
|
20
|
+
excon (0.16.3)
|
21
|
+
ffi (1.1.5)
|
22
|
+
fission (0.4.0)
|
23
|
+
CFPropertyList (~> 2.0.17)
|
24
|
+
fog (1.6.0)
|
25
|
+
builder
|
26
|
+
excon (~> 0.14)
|
27
|
+
formatador (~> 0.2.0)
|
28
|
+
mime-types
|
29
|
+
multi_json (~> 1.0)
|
30
|
+
net-scp (~> 1.0.4)
|
31
|
+
net-ssh (>= 2.1.3)
|
32
|
+
nokogiri (~> 1.5.0)
|
33
|
+
ruby-hmac
|
34
|
+
formatador (0.2.3)
|
35
|
+
gherkin (2.11.2)
|
36
|
+
json (>= 1.4.6)
|
37
|
+
grit (2.5.0)
|
38
|
+
diff-lcs (~> 1.1)
|
39
|
+
mime-types (~> 1.15)
|
40
|
+
posix-spawn (~> 0.3.6)
|
41
|
+
highline (1.6.15)
|
42
|
+
i18n (0.6.1)
|
43
|
+
json (1.5.4)
|
44
|
+
libxml-ruby (2.3.3)
|
45
|
+
log4r (1.1.10)
|
46
|
+
mime-types (1.19)
|
47
|
+
multi_json (1.3.6)
|
48
|
+
mysql2 (0.2.18)
|
49
|
+
net-scp (1.0.4)
|
50
|
+
net-ssh (>= 1.99.1)
|
51
|
+
net-ssh (2.2.2)
|
52
|
+
nokogiri (1.5.5)
|
53
|
+
open4 (1.3.0)
|
54
|
+
popen4 (0.1.2)
|
55
|
+
Platform (>= 0.4.0)
|
56
|
+
open4 (>= 0.4.0)
|
57
|
+
posix-spawn (0.3.6)
|
58
|
+
progressbar (0.11.0)
|
59
|
+
rake (0.9.2.2)
|
60
|
+
rspec (2.11.0)
|
61
|
+
rspec-core (~> 2.11.0)
|
62
|
+
rspec-expectations (~> 2.11.0)
|
63
|
+
rspec-mocks (~> 2.11.0)
|
64
|
+
rspec-core (2.11.1)
|
65
|
+
rspec-expectations (2.11.3)
|
66
|
+
diff-lcs (~> 1.1.3)
|
67
|
+
rspec-mocks (2.11.3)
|
68
|
+
ruby-hmac (0.4.0)
|
69
|
+
ruby-vnc (1.0.1)
|
70
|
+
thor (0.16.0)
|
71
|
+
vagrant (1.0.5)
|
72
|
+
archive-tar-minitar (= 0.5.2)
|
73
|
+
childprocess (~> 0.3.1)
|
74
|
+
erubis (~> 2.7.0)
|
75
|
+
i18n (~> 0.6.0)
|
76
|
+
json (~> 1.5.1)
|
77
|
+
log4r (~> 1.1.9)
|
78
|
+
net-scp (~> 1.0.4)
|
79
|
+
net-ssh (~> 2.2.2)
|
80
|
+
veewee (0.3.0.beta2)
|
81
|
+
ansi (~> 1.3.0)
|
82
|
+
childprocess
|
83
|
+
cucumber (>= 1.0.0)
|
84
|
+
fission (= 0.4.0)
|
85
|
+
fog (~> 1.4)
|
86
|
+
grit
|
87
|
+
highline
|
88
|
+
i18n
|
89
|
+
net-ssh (~> 2.2.0)
|
90
|
+
popen4 (~> 0.1.2)
|
91
|
+
progressbar
|
92
|
+
rspec (~> 2.5)
|
93
|
+
ruby-vnc (~> 1.0.0)
|
94
|
+
thor (> 0.14)
|
95
|
+
vagrant (>= 0.9)
|
96
|
+
|
97
|
+
PLATFORMS
|
98
|
+
ruby
|
99
|
+
|
100
|
+
DEPENDENCIES
|
101
|
+
childprocess
|
102
|
+
erubis
|
103
|
+
mysql2
|
104
|
+
rspec
|
105
|
+
veewee (= 0.3.0.beta2)
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2012 Harry Wilkinson
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# Servitor
|
2
|
+
|
3
|
+
Servitor is a tool for composing a Service Oriented Architecture (SOA) from a network
|
4
|
+
of services.
|
5
|
+
|
6
|
+
It is recommended that services should conform to the [12-Factor App](http://www.12factor.net/)
|
7
|
+
methodology.
|
8
|
+
|
9
|
+
Each service includes a Servicefile at the root of its codebase, declaring various properties
|
10
|
+
of the app that are fixed for any given version, such as:
|
11
|
+
|
12
|
+
* Runtime dependencies on other services
|
13
|
+
* Configurable variables
|
14
|
+
* Execution instructions
|
15
|
+
* Infrastructure requirements
|
16
|
+
* Resource dependencies
|
17
|
+
|
18
|
+
Given a set of required services, Servitor will build a dependency graph of those services
|
19
|
+
and all their implicit dependent services, construct a set of infrastructure upon which those
|
20
|
+
services can run, configure them appropriately, and execute them.
|
21
|
+
|
22
|
+
The principal goal of Servitor is to support the development of services that are part of an SOA,
|
23
|
+
allowing developers to perform local development and testing with real backing services rather
|
24
|
+
than using mocked local services or shared remote services. This is achieved using
|
25
|
+
[Vagrant](http://vagrantup.com) to create local virtual machines, isolating each service and
|
26
|
+
allowing each service have different infrastructure requirements.
|
27
|
+
|
28
|
+
In the longer term, Servitor should become more flexible with alternative implementations of
|
29
|
+
its features, such as provisioning infrastructure from cloud providers (AWS, OpenStack, etc.)
|
30
|
+
|
31
|
+
For now, perhaps the most informative way to describe Servitor is with an example Servicefile:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
# The unique name of this service in the SOA
|
35
|
+
name 'my-service'
|
36
|
+
|
37
|
+
# This service may depend on any number of other services, identified by their
|
38
|
+
# unique names in the SOA. Some services may depend on multiple instances of
|
39
|
+
# another service with different properties; this needs to be figured out.
|
40
|
+
depends_on %w( service2 service3 )
|
41
|
+
|
42
|
+
# Specify the infrastructure requirements for this service. Anything that is
|
43
|
+
# not specified will revert to some defaults. There may be additional
|
44
|
+
# infrastructure used in any given infrastructure provider implementation, but
|
45
|
+
# this is the minimum set required by the app.
|
46
|
+
infrastructure_requirements do
|
47
|
+
os_name 'ubuntu'
|
48
|
+
os_version '10.04'
|
49
|
+
os_arch '64'
|
50
|
+
ruby_version '1.9.2-p290'
|
51
|
+
end
|
52
|
+
|
53
|
+
# This should define the commands to use for the 12-factor build and release
|
54
|
+
# steps (also aliased as configure and execute). Any other services that this
|
55
|
+
# service depends on will be started up first. If there are mutual dependencies
|
56
|
+
# then they should both be built and configured and then both run.
|
57
|
+
deployment_stages do
|
58
|
+
build 'echo "running build task"'
|
59
|
+
release 'echo "running release task"'
|
60
|
+
run 'rackup -p <%= '<%= @port %>' %>'
|
61
|
+
end
|
62
|
+
|
63
|
+
# Any configurable variables for this app are defined here. If the app is to
|
64
|
+
# receive configuration data then it *must* be included in this block. This
|
65
|
+
# includes details of any backing resources (databases, queues, etc.) that need
|
66
|
+
# to be attached. If any of these are missing we can catch that problem without
|
67
|
+
# even starting the app. We can also automate the configuration details that
|
68
|
+
# tell the app how to communicate with other services it depends on.
|
69
|
+
configuration do
|
70
|
+
var 'config_var1'
|
71
|
+
var 'config_var2'
|
72
|
+
var 'config_var3', :default => 'some default'
|
73
|
+
var 'service2_url', :domain_for => 'service2', :format => 'https://<%= '<%= @value %>' %>'
|
74
|
+
|
75
|
+
# If the service depends on attached resources, each resource can be configured
|
76
|
+
# this way
|
77
|
+
resource 'database', :type => 'mysql' do
|
78
|
+
var 'database_host', :from => 'hostname'
|
79
|
+
var 'database_name', :from => 'database_name'
|
80
|
+
var 'database_username', :from => 'username'
|
81
|
+
var 'database_password', :from => 'password'
|
82
|
+
end
|
83
|
+
|
84
|
+
# This configuration script will run in the context of another service, with the specified
|
85
|
+
# arguments taken from the local configuration. The specified vars will be set from the output
|
86
|
+
# of the script
|
87
|
+
dependency_script 'register', :service => 'service2' do
|
88
|
+
arg 'name', :from => :service_name
|
89
|
+
var 'service_uuid'
|
90
|
+
var 'private_key'
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# This is a configuration script provided by this service to other services that depend
|
95
|
+
# upon this one. The specified args must be provided by the other service, and the given
|
96
|
+
# command will run with those args set as environment variables, with output sent to stdout
|
97
|
+
# as a single YAML mapping node.
|
98
|
+
provides_script 'frob' do
|
99
|
+
command 'bundle exec ruby frob.rb'
|
100
|
+
arg 'frobber'
|
101
|
+
end
|
102
|
+
```
|
data/lib/cli/cli.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
## Runs dependent services in a SOA
|
4
|
+
|
5
|
+
module Servitor
|
6
|
+
class CLI
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
Servitor.root = Dir.pwd
|
10
|
+
end
|
11
|
+
|
12
|
+
def autoconfigure
|
13
|
+
|
14
|
+
SshKey.new(Servitor.ssh_dir).deploy
|
15
|
+
vagrantfile = Vagrantfile.new(services, Servitor.ssh_dir).generate
|
16
|
+
|
17
|
+
File.open(Servitor.vagrantfile, 'w') do |f|
|
18
|
+
f.write(vagrantfile)
|
19
|
+
end
|
20
|
+
|
21
|
+
#AppDeployer.deploy(service_definition, infrastructure, shared_service_info)
|
22
|
+
#AppBuilder.build(service_definition, infrastructure, shared_service_info)
|
23
|
+
#AppReleaser.release(service_definition, infrastructure, shared_service_info)
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
def start
|
28
|
+
main_box.up
|
29
|
+
end
|
30
|
+
|
31
|
+
def deploy
|
32
|
+
configuration_provider = YamlConfigurationProvider.new(File.join(Servitor.data_root, 'variables.yml'))
|
33
|
+
|
34
|
+
service_nodes.each do |service_node|
|
35
|
+
configuration_resolver = ConfigurationResolver.new(
|
36
|
+
service_node.service_definition,
|
37
|
+
configuration_provider,
|
38
|
+
services)
|
39
|
+
variables = configuration_resolver.variables
|
40
|
+
|
41
|
+
service = services.find {|s| s.name == service_node.service_definition.name}
|
42
|
+
box = VagrantBox.new(service.name)
|
43
|
+
EnvdirDeployer.new(main_box, service.name, service.vm_root, '.envdir', variables).deploy
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def up
|
48
|
+
autoconfigure
|
49
|
+
start
|
50
|
+
deploy
|
51
|
+
end
|
52
|
+
|
53
|
+
def halt
|
54
|
+
main_box.halt
|
55
|
+
end
|
56
|
+
|
57
|
+
def destroy
|
58
|
+
main_box.destroy
|
59
|
+
end
|
60
|
+
|
61
|
+
def reup
|
62
|
+
destroy
|
63
|
+
up
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def main_box
|
69
|
+
VagrantBox.new('servitor', Servitor.data_root)
|
70
|
+
end
|
71
|
+
|
72
|
+
def service_nodes
|
73
|
+
@service_nodes ||= begin
|
74
|
+
service_definition = ServiceLocator.locate
|
75
|
+
graph = ServiceGraph.build(service_definition)
|
76
|
+
ServiceGraphFlattener.flatten_to_startup_order(graph)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def service_box_names
|
81
|
+
@service_box_names ||= begin
|
82
|
+
names = {}
|
83
|
+
service_nodes.each do |service_node|
|
84
|
+
names[service_node] = InfrastructureProvisioner.provision(service_node.service_definition.infrastructure_requirements)
|
85
|
+
end
|
86
|
+
names
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def services
|
91
|
+
@services ||= begin
|
92
|
+
ServiceLinker.new(service_nodes, service_box_names).link(:first_port => 4000)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Servitor
|
2
|
+
|
3
|
+
class ConfigurationResolver
|
4
|
+
def initialize(service_definition, configuration_provider, services)
|
5
|
+
@service_definition = service_definition
|
6
|
+
@configuration_provider = configuration_provider
|
7
|
+
@services = services
|
8
|
+
end
|
9
|
+
|
10
|
+
def variables
|
11
|
+
@variables ||= begin
|
12
|
+
vars = resolve_variables(service_configuration.variables)
|
13
|
+
service_configuration.resources.each do |resource_configuration|
|
14
|
+
vars.merge!(resolve_resource_variables(resource_configuration)) do |key, _, _|
|
15
|
+
raise VariableConflict, "'#{key}' is defined more than once"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
vars
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class VariableConflict < StandardError; end
|
23
|
+
class DependentServiceNotFound < StandardError; end
|
24
|
+
class MissingRequiredVariable < StandardError; end
|
25
|
+
class ResourceNotFound < StandardError; end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def service_configuration
|
30
|
+
@service_definition.configuration
|
31
|
+
end
|
32
|
+
|
33
|
+
def provided_variables
|
34
|
+
@provided_variables ||= @configuration_provider.variables_for(@service_definition.name)
|
35
|
+
end
|
36
|
+
|
37
|
+
def resolve_variables(required_variables)
|
38
|
+
resolved_variables = {}
|
39
|
+
required_variables.map do |required_variable|
|
40
|
+
value = nil
|
41
|
+
if (service_name = required_variable.options[:domain_for])
|
42
|
+
service = @services.find{|s| s.name == service_name}
|
43
|
+
raise DependentServiceNotFound, service_name unless service
|
44
|
+
value = service.ip_address
|
45
|
+
else
|
46
|
+
value = provided_variables[required_variable.name]
|
47
|
+
end
|
48
|
+
|
49
|
+
if value.nil?
|
50
|
+
if (default = required_variable.options[:default])
|
51
|
+
value = default
|
52
|
+
else
|
53
|
+
raise MissingRequiredVariable, required_variable.name
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
if (format = required_variable.options[:format])
|
58
|
+
value = Erubis::Eruby.new(format).result(:value => value)
|
59
|
+
end
|
60
|
+
resolved_variables[required_variable.name] = value
|
61
|
+
end
|
62
|
+
resolved_variables
|
63
|
+
end
|
64
|
+
|
65
|
+
def resolve_resource_variables(resource_configuration)
|
66
|
+
resolved_variables = {}
|
67
|
+
resource = find_resource(resource_configuration)
|
68
|
+
raise ResourceNotFound, resource_configuration.name unless resource
|
69
|
+
resource_configuration.variables.each do |required_variable|
|
70
|
+
source_attribute = required_variable.options[:from]
|
71
|
+
raise ResourceAttributeNotFound, source_attribute unless resource.key?(source_attribute)
|
72
|
+
resolved_variables[required_variable.name] = resource[source_attribute]
|
73
|
+
end
|
74
|
+
resolved_variables
|
75
|
+
end
|
76
|
+
|
77
|
+
def resources
|
78
|
+
@resources ||= @configuration_provider.resources_for(@service_definition.name)
|
79
|
+
end
|
80
|
+
|
81
|
+
def find_resource(resource_configuration)
|
82
|
+
if resource_configuration.options[:type] == 'mysql'
|
83
|
+
mysql_service = @services.find{|s| s.name == 'mysql'}
|
84
|
+
resource = {
|
85
|
+
'hostname' => mysql_service.ip_address,
|
86
|
+
'database_name' => @service_definition.name,
|
87
|
+
'username' => MysqlResource::USERNAME,
|
88
|
+
'password' => MysqlResource::PASSWORD,
|
89
|
+
}
|
90
|
+
else
|
91
|
+
resource = resources[resource_configuration.name]
|
92
|
+
end
|
93
|
+
resource
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Servitor
|
2
|
+
|
3
|
+
class YamlConfigurationProvider
|
4
|
+
|
5
|
+
def initialize(file_path)
|
6
|
+
@file_path = file_path
|
7
|
+
end
|
8
|
+
|
9
|
+
def variables_for(service_name)
|
10
|
+
services = yaml['services'] || {}
|
11
|
+
service_vars = services[service_name] || {}
|
12
|
+
common_vars = services['common'] || {}
|
13
|
+
common_vars.merge(service_vars)
|
14
|
+
end
|
15
|
+
|
16
|
+
def resources_for(service_name)
|
17
|
+
resources = yaml['resources'] || {}
|
18
|
+
resources[service_name] || {}
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def yaml
|
24
|
+
@yaml ||= YAML.load_file(@file_path)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|