servitor 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/.gitignore +1 -0
  2. data/Gemfile +10 -0
  3. data/Gemfile.lock +105 -0
  4. data/LICENSE +19 -0
  5. data/README.md +102 -0
  6. data/lib/cli/cli.rb +96 -0
  7. data/lib/configuration/configuration.rb +2 -0
  8. data/lib/configuration/configuration_resolver.rb +97 -0
  9. data/lib/configuration/yaml_configuration_provider.rb +28 -0
  10. data/lib/control/control.rb +0 -0
  11. data/lib/deployment/bundle_deployer.rb +14 -0
  12. data/lib/deployment/deployment.rb +2 -0
  13. data/lib/deployment/envdir_deployer.rb +34 -0
  14. data/lib/helpers/child_process_helper.rb +41 -0
  15. data/lib/helpers/helpers.rb +1 -0
  16. data/lib/infrastructure/infrastructure.rb +16 -0
  17. data/lib/infrastructure/infrastructure_provisioner.rb +25 -0
  18. data/lib/infrastructure/infrastructure_requirements.rb +25 -0
  19. data/lib/provisioners/Vagrantfile.erb +16 -0
  20. data/lib/provisioners/provisioners.rb +6 -0
  21. data/lib/provisioners/ssh_key.rb +22 -0
  22. data/lib/provisioners/vagrant_box.rb +116 -0
  23. data/lib/provisioners/vagrant_box_provisioner.rb +37 -0
  24. data/lib/provisioners/vagrant_box_ruby_installer.rb +34 -0
  25. data/lib/provisioners/vagrantfile.rb +34 -0
  26. data/lib/provisioners/veewee_box.rb +91 -0
  27. data/lib/resource/mysql_resource.rb +33 -0
  28. data/lib/resource/resource.rb +1 -0
  29. data/lib/service/service.rb +12 -0
  30. data/lib/service/service_definition/Servicefile.erb +54 -0
  31. data/lib/service/service_definition/deployment_stages.rb +16 -0
  32. data/lib/service/service_definition/resource_configuration.rb +14 -0
  33. data/lib/service/service_definition/service_configuration.rb +19 -0
  34. data/lib/service/service_definition/service_definition.rb +49 -0
  35. data/lib/service/service_definition/variable.rb +22 -0
  36. data/lib/service/service_file_parser.rb +21 -0
  37. data/lib/service/service_graph/service_graph.rb +58 -0
  38. data/lib/service/service_graph/service_graph_flattener.rb +34 -0
  39. data/lib/service/service_graph/service_graph_node.rb +15 -0
  40. data/lib/service/service_linker.rb +56 -0
  41. data/lib/service/service_locator.rb +43 -0
  42. data/lib/servitor.rb +47 -0
  43. data/servitor.gemspec +18 -0
  44. data/spec/provisioners/vagrant_basebox_provisioner_spec.rb +63 -0
  45. data/spec/provisioners/vagrant_box_ruby_provisioner_spec.rb +48 -0
  46. data/spec/spec_helper.rb +7 -0
  47. 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,14 @@
1
+ module Servitor
2
+
3
+ class ResourceConfiguration
4
+ include HasVariables
5
+
6
+ attr_reader :name, :options
7
+
8
+ def initialize(name, options=nil)
9
+ @name = name
10
+ @options = options || {}
11
+ end
12
+ end
13
+
14
+ 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