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