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