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
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'veewee'
|
2
|
+
|
3
|
+
module Servitor
|
4
|
+
|
5
|
+
class VeeweeBox
|
6
|
+
|
7
|
+
include ChildProcessHelper
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def find(name)
|
11
|
+
definition = self.veewee.definitions[name]
|
12
|
+
VeeweeBox.new(name, nil, nil) if definition # we don't have the os name & version available
|
13
|
+
end
|
14
|
+
|
15
|
+
def veewee
|
16
|
+
@veewee ||= Veewee::Environment.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def vagrant
|
20
|
+
VagrantBox.vagrant
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :name, :os_name, :os_version
|
25
|
+
|
26
|
+
def initialize(name, os_name, os_version, os_arch=nil)
|
27
|
+
raise InvalidNameError, name unless name =~ /^[a-zA-Z0-9\-]+$/
|
28
|
+
@name = name
|
29
|
+
@os_name = os_name
|
30
|
+
@os_version = os_version
|
31
|
+
@os_arch = os_arch || '64'
|
32
|
+
end
|
33
|
+
|
34
|
+
def define
|
35
|
+
execute_child_process('vagrant', 'basebox', 'define', @name, template_name)
|
36
|
+
end
|
37
|
+
|
38
|
+
def build(options={})
|
39
|
+
build_args = ['vagrant', 'basebox', 'build', @name, '--auto']
|
40
|
+
build_args << '--nogui' if options[:nogui]
|
41
|
+
execute_child_process(*build_args)
|
42
|
+
end
|
43
|
+
|
44
|
+
%w(up destroy).each do |method_name|
|
45
|
+
define_method(method_name) do
|
46
|
+
begin
|
47
|
+
execute_child_process('vagrant', 'basebox', method_name, @name)
|
48
|
+
rescue StandardError => e
|
49
|
+
raise e, "#{method_name}: #{e.message}", e.backtrace
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
%w(undefine validate export halt).each do |method_name|
|
55
|
+
define_method(method_name) do
|
56
|
+
begin
|
57
|
+
execute_child_process('vagrant', 'basebox', method_name, @name)
|
58
|
+
rescue StandardError => e
|
59
|
+
raise e, "#{method_name}: #{e.message}", e.backtrace
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def template_name
|
67
|
+
@template_name ||= begin
|
68
|
+
# We try to find the best match from the available templates with some educated guessing.
|
69
|
+
os_templates = []
|
70
|
+
self.class.veewee.templates.each do |name, template|
|
71
|
+
os_templates << template if name =~ Regexp.new(@os_name)
|
72
|
+
end
|
73
|
+
raise UnsupportedOsRequirementError, "Unsupported OS: #{@os_name}" if os_templates.empty?
|
74
|
+
|
75
|
+
version_templates = os_templates.select { |t| t.name =~ Regexp.new(@os_version) }
|
76
|
+
raise UnsupportedOsRequirementError, "Unsupported OS version: #{@os_version}" if version_templates.empty?
|
77
|
+
|
78
|
+
cpu_templates = version_templates.select { |t| t.name =~ Regexp.new(@os_arch) }
|
79
|
+
raise UnsupportedOsRequirementError, "Unsupported OS version architecture: #{@os_arch}" if cpu_templates.empty?
|
80
|
+
|
81
|
+
# If there are still several templates, use the one with the shortest name because it's probably the simplest.
|
82
|
+
template = cpu_templates.map(&:name).sort_by(&:length).first
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class UnsupportedOsRequirementError < StandardError; end
|
88
|
+
class InvalidNameError < StandardError; end
|
89
|
+
class VagrantBaseBoxError < StandardError; end
|
90
|
+
|
91
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Servitor
|
2
|
+
class MysqlResource
|
3
|
+
|
4
|
+
USERNAME = 'root'
|
5
|
+
PASSWORD = ''
|
6
|
+
|
7
|
+
def self.service_definition
|
8
|
+
@service_definition ||= begin
|
9
|
+
service_definition = ServiceDefinition.new
|
10
|
+
service_definition.name 'mysql'
|
11
|
+
service_definition.infrastructure_requirements do
|
12
|
+
os_name 'ubuntu'
|
13
|
+
os_version '10.04'
|
14
|
+
os_arch '64'
|
15
|
+
ruby_version '1.9.2-p290'
|
16
|
+
end
|
17
|
+
service_definition.deployment_stages do
|
18
|
+
build <<-BASH
|
19
|
+
which mysqld || sudo apt-get install mysql-server -y
|
20
|
+
BASH
|
21
|
+
release <<-BASH
|
22
|
+
SERVICE_IP = ifconfig eth1 | sed '/inet addr/!d;s/.*addr:\(.*\) Bcast.*$/\1/'
|
23
|
+
sudo sed -i '/^bind-address/s/127.0.0.1/$SERVICE_IP/g' /etc/mysql/my.cnf
|
24
|
+
BASH
|
25
|
+
run <<-BASH
|
26
|
+
sudo service mysql restart
|
27
|
+
BASH
|
28
|
+
end
|
29
|
+
service_definition
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
servitor_require 'mysql_resource'
|
@@ -0,0 +1,12 @@
|
|
1
|
+
servitor_require 'service_definition/service_definition'
|
2
|
+
servitor_require 'service_graph/service_graph'
|
3
|
+
servitor_require 'service_file_parser'
|
4
|
+
servitor_require 'service_locator'
|
5
|
+
servitor_require 'service_linker'
|
6
|
+
|
7
|
+
|
8
|
+
module Servitor
|
9
|
+
class Service
|
10
|
+
attr_accessor :name, :box, :ip_address, :root, :vm_root, :forwarded_ports, :service_node
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
|
2
|
+
# The unique name of this service in the SOA
|
3
|
+
name 'my-service'
|
4
|
+
|
5
|
+
# This service may depend on any number of other services, identified by their
|
6
|
+
# unique names in the SOA. Some services may depend on multiple instances of
|
7
|
+
# another service with different properties; this needs to be figured out.
|
8
|
+
depends_on %w( service2 service3 )
|
9
|
+
|
10
|
+
# Specify the infrastructure requirements for this service. Anything that is
|
11
|
+
# not specified will revert to some defaults. There may be additional
|
12
|
+
# infrastructure used in any given infrastructure provider implementation, but
|
13
|
+
# this is the minimum set required by the app.
|
14
|
+
infrastructure_requirements do
|
15
|
+
os_name 'ubuntu'
|
16
|
+
os_version '10.04'
|
17
|
+
os_arch '64'
|
18
|
+
ruby_version '1.9.2-p290'
|
19
|
+
end
|
20
|
+
|
21
|
+
# This should define the commands to use for the 12-factor build and release
|
22
|
+
# steps (also aliased as configure and execute). Any other services that this
|
23
|
+
# service depends on will be started up first. If there are mutual dependencies
|
24
|
+
# then they should both be configured and then both started. If the deployment
|
25
|
+
# tools need to interact with the app in any other app-specific way, that should
|
26
|
+
# also be defined here. Note that these are explicitly deployment-related
|
27
|
+
# operations; should we define any control tier operations here too?
|
28
|
+
deployment_stages do
|
29
|
+
build 'echo "running build task"'
|
30
|
+
release 'echo "running release task"'
|
31
|
+
run 'rackup -p <%= '<%= @port %>' %>'
|
32
|
+
end
|
33
|
+
|
34
|
+
# Any configurable variables for this app are defined here. If the app is to
|
35
|
+
# receive configuration data then it *must* be included in this block. This
|
36
|
+
# includes details of any backing resources (databases, queues, etc.) that need
|
37
|
+
# to be attached. If any of these are missing we can catch that problem without
|
38
|
+
# even starting the app. We can also automate the configuration details that
|
39
|
+
# tell the app how to communicate with other services it depends on.
|
40
|
+
configuration do
|
41
|
+
var 'config_var1'
|
42
|
+
var 'config_var2'
|
43
|
+
var 'config_var3', :default => 'some default'
|
44
|
+
var 'service2_url', :domain_for => 'service2', :format => 'https://<%= '<%= @value %>' %>'
|
45
|
+
|
46
|
+
# If the service depends on attached resources, each resource can be configured
|
47
|
+
# this way
|
48
|
+
resource 'database', :type => 'mysql' do
|
49
|
+
var 'database_host', :from => 'hostname'
|
50
|
+
var 'database_name', :from => 'database_name'
|
51
|
+
var 'database_username', :from => 'username'
|
52
|
+
var 'database_password', :from => 'password'
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Servitor
|
2
|
+
|
3
|
+
class DeploymentStages
|
4
|
+
|
5
|
+
%w(build release run).each do |attr|
|
6
|
+
define_method(attr) do |*args|
|
7
|
+
value = args.length > 0 ? args.first : nil
|
8
|
+
attr_sym = "@#{attr}".to_sym
|
9
|
+
instance_variable_set(attr_sym, value) unless value.nil?
|
10
|
+
instance_variable_get(attr_sym)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Servitor
|
2
|
+
|
3
|
+
class ServiceConfiguration
|
4
|
+
|
5
|
+
include HasVariables
|
6
|
+
|
7
|
+
def resource(name, options=nil, &block)
|
8
|
+
resource_configuration = ResourceConfiguration.new(name, options)
|
9
|
+
resource_configuration.instance_exec(&block) if block_given?
|
10
|
+
resources << resource_configuration
|
11
|
+
end
|
12
|
+
|
13
|
+
def resources
|
14
|
+
@resources ||= []
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
servitor_require 'variable'
|
2
|
+
servitor_require 'deployment_stages'
|
3
|
+
servitor_require 'resource_configuration'
|
4
|
+
servitor_require 'service_configuration'
|
5
|
+
servitor_require 'service_definition'
|
6
|
+
|
7
|
+
module Servitor
|
8
|
+
|
9
|
+
class ServiceDefinition
|
10
|
+
|
11
|
+
# these are part of the service configuration file
|
12
|
+
%w(name depends_on).each do |attr|
|
13
|
+
define_method(attr) do |*args|
|
14
|
+
value = args.length > 0 ? args.first : nil
|
15
|
+
attr_sym = "@#{attr}".to_sym
|
16
|
+
instance_variable_set(attr_sym, value) unless value.nil?
|
17
|
+
instance_variable_get(attr_sym)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# these are derived at load time
|
22
|
+
attr_reader :location, :service_root
|
23
|
+
|
24
|
+
def initialize(location=nil, service_root=nil)
|
25
|
+
@location = location
|
26
|
+
@service_root = service_root
|
27
|
+
@depends_on = []
|
28
|
+
@deployment_stages = DeploymentStages.new
|
29
|
+
@infrastructure_requirements = InfrastructureRequirements.new
|
30
|
+
@configuration = ServiceConfiguration.new
|
31
|
+
end
|
32
|
+
|
33
|
+
def deployment_stages(*args, &block)
|
34
|
+
@deployment_stages.instance_exec(&block) if block_given?
|
35
|
+
@deployment_stages
|
36
|
+
end
|
37
|
+
|
38
|
+
def infrastructure_requirements(&block)
|
39
|
+
@infrastructure_requirements.instance_exec(&block) if block_given?
|
40
|
+
@infrastructure_requirements
|
41
|
+
end
|
42
|
+
|
43
|
+
def configuration(&block)
|
44
|
+
@configuration.instance_exec(&block) if block_given?
|
45
|
+
@configuration
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Servitor
|
2
|
+
|
3
|
+
class Variable
|
4
|
+
attr_reader :name, :options
|
5
|
+
|
6
|
+
def initialize(name, options=nil)
|
7
|
+
@name = name
|
8
|
+
@options = options || {}
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module HasVariables
|
13
|
+
def var(name, options=nil)
|
14
|
+
variables << Variable.new(name, options)
|
15
|
+
end
|
16
|
+
|
17
|
+
def variables
|
18
|
+
@variables ||= []
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Servitor
|
2
|
+
|
3
|
+
class ServiceFileParser
|
4
|
+
|
5
|
+
# Parses the given service file, returning a service config
|
6
|
+
def self.parse(file, service_root)
|
7
|
+
begin
|
8
|
+
file_content = File.read(file)
|
9
|
+
ServiceDefinition.new(file, service_root).tap do |sc|
|
10
|
+
sc.instance_exec do
|
11
|
+
eval(file_content)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
rescue StandardError => e
|
15
|
+
raise e, "Error parsing #{file}: #{e.message}", e.backtrace
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
servitor_require 'service_graph_node'
|
2
|
+
servitor_require 'service_graph_flattener'
|
3
|
+
|
4
|
+
module Servitor
|
5
|
+
|
6
|
+
class ServiceGraph
|
7
|
+
|
8
|
+
attr_reader :service_nodes, :root
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@service_nodes = { }
|
12
|
+
end
|
13
|
+
|
14
|
+
# Takes a service definition and builds a graph of service definitions,
|
15
|
+
# returning the root node for that graph
|
16
|
+
def self.build(root_service_definition)
|
17
|
+
new.tap { |g| g.send(:build, root_service_definition) }
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def build(root_service_definition)
|
23
|
+
@root = find_or_build_node(root_service_definition)
|
24
|
+
end
|
25
|
+
|
26
|
+
def find_or_build_node(service_definition)
|
27
|
+
node = @service_nodes[service_definition.name]
|
28
|
+
return node if node
|
29
|
+
node = ServiceGraphNode.new(service_definition)
|
30
|
+
@service_nodes[service_definition.name] = node
|
31
|
+
add_dependent_nodes(node)
|
32
|
+
add_resource_nodes(node)
|
33
|
+
node
|
34
|
+
end
|
35
|
+
|
36
|
+
def add_dependent_nodes(node)
|
37
|
+
node.service_definition.depends_on.each do |dependency_name|
|
38
|
+
dependent_service_definition = ServiceLocator.locate(dependency_name)
|
39
|
+
add_dependent_service(node, dependent_service_definition)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def add_dependent_service(node, dependent_service_definition)
|
44
|
+
dependent_node = find_or_build_node(dependent_service_definition)
|
45
|
+
node.depends_on_nodes << dependent_node if dependent_node
|
46
|
+
dependent_node.depended_on_by_nodes << node
|
47
|
+
end
|
48
|
+
|
49
|
+
def add_resource_nodes(node)
|
50
|
+
node.service_definition.configuration.resources.each do |resource_configuration|
|
51
|
+
if resource_configuration.options[:type] == 'mysql'
|
52
|
+
add_dependent_service(node, MysqlResource.service_definition)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Servitor
|
2
|
+
|
3
|
+
class ServiceGraphFlattener
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
# Takes a service graph and returns the service configs in service startup order.
|
8
|
+
# This means that services in the list depend only on other services
|
9
|
+
# that appear later in the list.
|
10
|
+
def flatten_to_startup_order(graph)
|
11
|
+
nodes = []
|
12
|
+
remaining_nodes = graph.service_nodes.values
|
13
|
+
while remaining_nodes.any?
|
14
|
+
found = false
|
15
|
+
remaining_nodes.dup.each do |node|
|
16
|
+
if node.depends_on_nodes.none? { |depends_on_node| remaining_nodes.include?(depends_on_node) }
|
17
|
+
found = true
|
18
|
+
nodes << node
|
19
|
+
remaining_nodes.delete(node)
|
20
|
+
break
|
21
|
+
end
|
22
|
+
end
|
23
|
+
raise CyclicDependencyError unless found
|
24
|
+
end
|
25
|
+
nodes
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
class CyclicDependencyError < StandardError; end
|
33
|
+
|
34
|
+
end
|