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