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
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ *.erb.cache
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'veewee', '0.3.0.beta2'
4
+ gem 'childprocess'
5
+ gem 'erubis'
6
+ gem 'mysql2'
7
+
8
+ group :test do
9
+ gem 'rspec'
10
+ end
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,2 @@
1
+ servitor_require 'configuration_resolver'
2
+ servitor_require 'yaml_configuration_provider'
@@ -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