proctor 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/Proctorfile +4 -0
  2. data/README.md +97 -2
  3. data/Rakefile +15 -1
  4. data/bin/proctor +92 -88
  5. data/lib/proctor/app_config.rb +23 -0
  6. data/lib/proctor/app_file.rb +40 -1
  7. data/lib/proctor/cmd_state.rb +39 -0
  8. data/lib/proctor/manager.rb +8 -2
  9. data/lib/proctor/presenter/list.rb +67 -0
  10. data/lib/proctor/presenter/render.rb +39 -0
  11. data/lib/proctor/presenter/show.rb +71 -0
  12. data/lib/proctor/service.rb +8 -0
  13. data/lib/proctor/template_config.rb +22 -0
  14. data/lib/proctor/template_file.rb +44 -0
  15. data/lib/proctor/version.rb +1 -1
  16. data/lib/proctor.rb +1 -1
  17. data/proctor/Proctorfile +44 -20
  18. data/proctor/templates/foreman_default_n.erb +9 -0
  19. data/proctor/templates/monit_default_s.erb +6 -0
  20. data/proctor/templates/monit_faye_s.erb +7 -0
  21. data/{lib/proctor/app_global.rb → proctor/templates/monit_web_s.erb} +0 -0
  22. data/{lib/proctor/config_global.rb → proctor/templates/upstart_default_n.erb} +0 -0
  23. data/proctor/templates/upstart_default_s.erb +7 -0
  24. data/proctor/templates/upstart_faye_s.erb +9 -0
  25. data/proctor/{managers/MonitDefault.rb → templates/upstart_passenger_s.erb} +0 -0
  26. data/proctor.gemspec +2 -0
  27. data/spec/acceptance/help_spec.rb +17 -1
  28. data/spec/acceptance/list_spec.rb +17 -0
  29. data/spec/spec_helper.rb +7 -4
  30. data/spec/support/aruba.rb +2 -2
  31. data/spec/unit/app_config_spec.rb +30 -0
  32. data/spec/unit/app_file_spec.rb +36 -5
  33. data/spec/unit/manager_spec.rb +16 -7
  34. data/spec/unit/service_spec.rb +20 -0
  35. data/spec/unit/template_config_spec.rb +12 -0
  36. data/spec/unit/template_file_spec.rb +16 -0
  37. data/spec/unit/template_spec.rb +3 -2
  38. metadata +61 -25
  39. data/lib/proctor/config_file.rb +0 -5
  40. data/lib/proctor/export.rb +0 -5
  41. data/lib/proctor/job.rb +0 -4
  42. data/proctor/managers/MonitFaye.rb +0 -0
  43. data/proctor/managers/MonitWeb.rb +0 -0
  44. data/proctor/managers/UpstartDefault.rb +0 -0
  45. data/proctor/managers/UpstartFaye.rb +0 -0
  46. data/proctor/managers/UpstartWeb.rb +0 -0
  47. data/proctor/templates/monit_default.erb +0 -0
  48. data/proctor/templates/monit_faye.erb +0 -0
  49. data/proctor/templates/monit_web.erb +0 -0
  50. data/proctor/templates/upstart_default.erb +0 -0
  51. data/proctor/templates/upstart_faye.erb +0 -0
  52. data/proctor/templates/upstart_web.erb +0 -0
  53. data/spec/unit/config_file_spec.rb +0 -9
  54. data/spec/unit/export_spec.rb +0 -9
  55. data/spec/unit/job_spec.rb +0 -9
data/Proctorfile ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ managers:
3
+ upstart:
4
+ export_directory: '/tmp/test'
data/README.md CHANGED
@@ -1,6 +1,15 @@
1
1
  # Proctor
2
2
 
3
- TODO: Write a gem description
3
+ Proctor is a configurable process manager that supports:
4
+ * customized upstart/init scripts
5
+ * separate start/stop/restart commands
6
+ * multiple managent tools like monit & upstart
7
+ * lightweight process monitoring
8
+ * config file cleanup
9
+
10
+ Proctor was inspired by the excellent Foreman gem. Proctor
11
+ seeks to retain the clean and simple design of Foreman,
12
+ while adding a bit more configurability.
4
13
 
5
14
  ## Installation
6
15
 
@@ -18,7 +27,93 @@ Or install it yourself as:
18
27
 
19
28
  ## Usage
20
29
 
21
- TODO: Write usage instructions here
30
+ Start by running 'proctor help'.
31
+
32
+ Proctor is useful when an application requires a suite of
33
+ independent processes which need to be managed and monitored.
34
+
35
+ For development, Proctor brings up all your processes in a
36
+ single screen, just like Foreman.
37
+
38
+ For production, Proctor is designed to work in concert with
39
+ config tools like Puppet/Chef, and with deploy tools like
40
+ Capistrano. Proctor can create tailored application
41
+ manifests for single or multi-server deployments.
42
+
43
+ ## Background
44
+
45
+ With Proctor, all of your application processes are described in
46
+ a single YAML file, called a Proctorfile.
47
+
48
+ In the Proctorfile, you can designate one or more Nodes. Example
49
+ Nodes include `web`, `db`, `backup`. Each node has a list of
50
+ Services.
51
+
52
+ nodes:
53
+ web:
54
+ - passenger
55
+ - faye
56
+ - jobq
57
+ - renderpro
58
+ - redis
59
+ db:
60
+ - postgres-shared
61
+ backup:
62
+ - postgres
63
+
64
+ Services are defined independently. Example Services include
65
+ `unicorn`, `faye`, `postgres` and `redis`.
66
+
67
+ services:
68
+ unicorn:
69
+ start_command: "bin/unicorn --log_file shared/log/<%= name %>.log"
70
+ stop_command: "qwer"
71
+ passenger:
72
+ start_command: "bin/passenger --log_file shared/log/<%= name %>.log"
73
+ stop_command: "qwer"
74
+ faye:
75
+ start_command: "qwer"
76
+ export.monit.memory_limit: "<%= ForemanEnv.production? ? '20MB' : '10MB' %>"
77
+ exports_to: ['monit', 'upstart']
78
+ start_command: "script/faye_script.sh"
79
+ port: 2343
80
+
81
+ Services are controlled and monitored by Managers. Example
82
+ Managers include `upstart`, `monit`, and `foreman`.
83
+
84
+ managers:
85
+ upstart:
86
+ export_directory: "/etc/init"
87
+ start_command: "/sbin/init start <%= app_name %>"
88
+ stop_command: "/sbin/init stop <%= app_name %>"
89
+ status_command: "/sbin/init status <%= app_name %>"
90
+ monit:
91
+ export_directory: "/etc/monit/conf.d"
92
+ reload_command: "monit reload"
93
+ stop_command: "monit quit"
94
+ start_command: "monit"
95
+ status_command: "monit summary"
96
+ pidfile: "/a/b/c"
97
+ foreman:
98
+ start_command: "foreman start"
99
+ use_master_template: true
100
+ use_worker_template: false
101
+
102
+ In a Proctor run, ERB templates are used to create config
103
+ files that are exported to managers. Different Templates
104
+ can be established for each type of Service and Manager.
105
+
106
+ # Monit config file (template: <%= original_template_file %>)
107
+ # Generated by Proctor at <%= Time.now %>
108
+ # Hostname: <%= ENV['host'] %>
109
+ # Target Directory: <%= export_directory %>
110
+
111
+ check process <%= role_name %> with pidfile <%= pidfile %>
112
+ start program = "<%= start_command %>"
113
+ stop program = "<%= stop_command %>"
114
+
115
+ In a Proctor run, multiple Proctorfiles can be referenced, and
116
+ their values will be merged together.
22
117
 
23
118
  ## Contributing
24
119
 
data/Rakefile CHANGED
@@ -3,7 +3,21 @@ require "bundler/gem_tasks"
3
3
 
4
4
  desc "Run all specs"
5
5
  task :rspec do
6
- system "rspec"
6
+ system "rspec -d"
7
+ end
8
+
9
+ namespace :rspec do
10
+
11
+ desc "Run unit specs"
12
+ task :unit do
13
+ system "rspec -d spec/unit"
14
+ end
15
+
16
+ desc "Run acceptance specs"
17
+ task :acceptance do
18
+ system "rspec -d spec/acceptance"
19
+ end
20
+
7
21
  end
8
22
 
9
23
  task :default => :rspec
data/bin/proctor CHANGED
@@ -3,26 +3,20 @@
3
3
  $: << File.expand_path(File.dirname(File.realpath(__FILE__)) + '/../lib')
4
4
 
5
5
  require 'rubygems'
6
+ require 'debugger'
6
7
  require 'gli'
7
8
  require 'proctor'
8
9
 
9
10
  include GLI
11
+ include Proctor
12
+
13
+ APP_CONFIG=
10
14
 
11
15
  # ---- global options ----------------------------------------------------
12
16
 
13
17
  version Proctor::VERSION
14
18
  program_desc 'proctor - an application process manager'
15
19
 
16
- desc 'Root Directory'
17
- default_value Dir.pwd
18
- arg_name 'APP_ROOT'
19
- flag [:d,'app-root']
20
-
21
- desc 'Application Environment'
22
- default_value "production"
23
- arg_name 'ENV'
24
- flag [:e,:environment]
25
-
26
20
  desc 'Select Proctorfile'
27
21
  default_value 'Proctorfile'
28
22
  arg_name 'PROCTORFILE'
@@ -32,93 +26,106 @@ flag [:f,:proctorfile]
32
26
 
33
27
  desc 'Validates your application Proctorfile'
34
28
  command :check do |c|
35
- c.action do |global_options,options,args|
36
- puts "I AM IN CHECK"
37
- # If you have any errors, just raise them
38
- # raise "that command made no sense"
29
+ c.action do |global,options,args|
30
+ puts "OK"
39
31
  end
40
32
  end
41
33
 
42
- desc 'Clean up generated files'
34
+ desc 'Removes exported proctor assets'
35
+ long_desc <<-EOF
36
+ Requires a NODE and a MANAGER name. To see
37
+ all NODEs and MANAGERs, use 'proctor list'.
38
+ EOF
39
+ arg_name "NODE MANAGER"
43
40
  command :cleanup do |c|
44
- c.action do |global_options,options,args|
45
- puts "I AM IN CHECK"
46
- # If you have any errors, just raise them
47
- # raise "that command made no sense"
41
+ c.action do |global,options,args|
42
+ puts "CLEANUP"
48
43
  end
49
44
  end
50
45
 
51
- desc 'Exports the app to another tool'
52
- arg_name 'Describe arguments to export here'
46
+ desc 'Exports assets to a management tool'
47
+ long_desc <<-EOF
48
+ Requires a NODE and a MANAGER name. To see
49
+ all NODEs and MANAGERs, use 'proctor list'.
50
+ EOF
51
+ arg_name "NODE MANAGER"
53
52
  command :export do |c|
54
-
55
- c.desc 'Format'
56
- c.default_value 'upstart'
57
- c.arg_name 'FORMAT'
58
- c.flag [:f, :format]
59
-
60
- c.desc 'Location'
61
- c.default_value Dir.pwd
62
- c.arg_name 'DIRECTORY'
63
- c.flag [:d, :directory]
64
-
65
- c.action do |global_options,options,args|
66
- puts "I AM IN EXPORT"
53
+ c.action do |global,options,args|
54
+ puts "EXPORT"
67
55
  end
68
-
69
56
  end
70
57
 
71
- desc 'Generates local templates'
72
- arg_name 'Describe arguments to init here'
73
- command :generate do |c|
74
- c.action do |global_options,options,args|
75
- puts "I AM IN INIT"
76
- end
77
- end
58
+ desc 'Lists handles, names and paths for proctor assets'
59
+ long_desc <<-EOF
60
+ Lists the name or path of each Proctor asset. Asset types include Templates,
61
+ AppFiles, Managers, Services, and Nodes. Managers, Services and Nodes are
62
+ defined within an AppFile.
78
63
 
79
- desc 'Creates a starter Procfile '
80
- arg_name 'Describe arguments to init here'
81
- command :init do |c|
82
- c.action do |global_options,options,args|
83
- puts "I AM IN INIT"
64
+ AppFile values from multiple files are merged together. When there is a conflict,
65
+ the last value take precedence.
66
+ EOF
67
+ command :list do |c|
68
+ opt_arr = %w(templates appfiles managers services manifest all)
69
+ opt_str = opt_arr.join('|')
70
+ c.desc "One of: [#{opt_str}]"
71
+ c.default_value 'all'
72
+ c.arg_name 'ASSET_TYPE'
73
+ c.flag [:t, :type]
74
+ c.action do |global,options,args|
75
+ unless opt_arr.include?(options[:type])
76
+ raise "unrecognized type (#{options[:type]}) must be one of [#{opt_str}]"
77
+ end
78
+ Presenter::List.new(global, options, args).render
84
79
  end
85
80
  end
86
81
 
87
- desc 'Lists available formats'
88
- arg_name 'Describe arguments to init here'
89
- command :list do |c|
90
- c.action do |global_options,options,args|
91
- puts "I AM IN LIST"
82
+ desc 'Renders an ERB template to the console'
83
+ arg_name 'MANAGER SERVICE'
84
+ long_desc <<-EOF
85
+ Requires a MANAGER and a SERVICE. To see all
86
+ MANAGERs and SERVICEs, use 'proctor list'.
87
+ EOF
88
+ command :render do |c|
89
+ c.action do |global,options,args|
90
+ raise "usage 'render ROLE MANAGER'" unless args.length == 2
91
+
92
+ Presenter::Render.new(global, options, args).render
92
93
  end
93
94
  end
94
95
 
95
- desc 'Run the application (development mode)'
96
- arg_name 'Describe arguments to run here'
97
- command :run do |c|
98
- c.action do |global_options,options,args|
99
- puts "I AM IN RUN"
96
+ desc 'Shows content of a proctor asset'
97
+ long_desc <<-EOF
98
+ Displays a raw asset (Template, AppFile, Manager, Service, Node)
99
+ to the console. View asset handles using 'proctor list'. (asset
100
+ handles are three character strings at the start of each line)
101
+ EOF
102
+ arg_name 'HANDLE'
103
+ command :show do |c|
104
+ c.action do |global,options,args|
105
+ raise "need a handle!" if args.length != 1
106
+ Presenter::Show.new(global, options, args).show
100
107
  end
101
108
  end
102
109
 
103
- desc 'Start the application (production mode)'
104
- arg_name 'Describe arguments to start here'
110
+ desc 'Starts the application'
111
+ arg_name "NODE MANAGER"
112
+ long_desc <<-EOF
113
+ Requires a NODE and a MANAGER name. To see
114
+ all NODEs and MANAGERs, use 'proctor list'.
115
+ EOF
105
116
  command :start do |c|
106
- c.desc 'Describe a switch to start'
107
- c.switch :s
108
117
 
109
- c.desc 'Describe a flag to start'
110
- c.default_value 'default'
111
- c.flag :f
112
118
  c.action do |global_options,options,args|
113
- puts "I AM IN START"
114
-
115
- # If you have any errors, just raise them
116
- # raise "that command made no sense"
119
+ puts "START"
117
120
  end
118
121
  end
119
122
 
120
- desc 'Stops the application (production mode)'
121
- arg_name 'Describe arguments to start here'
123
+ desc 'Stops the application'
124
+ arg_name "NODE MANAGER"
125
+ long_desc <<-EOF
126
+ Requires a NODE and a MANAGER name. To see
127
+ all NODEs and MANAGERs, use 'proctor list'.
128
+ EOF
122
129
  command :stop do |c|
123
130
  c.desc 'Describe a switch to start'
124
131
  c.switch :s
@@ -127,15 +134,17 @@ command :stop do |c|
127
134
  c.default_value 'default'
128
135
  c.flag :f
129
136
  c.action do |global_options,options,args|
130
- puts "I AM IN START"
131
-
132
- # If you have any errors, just raise them
137
+ puts "I AM IN STOP"
133
138
  # raise "that command made no sense"
134
139
  end
135
140
  end
136
141
 
137
- desc 'Shows the application status (production mode)'
138
- arg_name 'Describe arguments to start here'
142
+ desc 'Displays the application status'
143
+ arg_name "NODE MANAGER"
144
+ long_desc <<-EOF
145
+ Requires a NODE and a MANAGER name. To see
146
+ all NODEs and MANAGERs, use 'proctor list'.
147
+ EOF
139
148
  command :status do |c|
140
149
  c.desc 'Describe a switch to start'
141
150
  c.switch :s
@@ -143,11 +152,8 @@ command :status do |c|
143
152
  c.desc 'Describe a flag to start'
144
153
  c.default_value 'default'
145
154
  c.flag :f
146
- c.action do |global_options,options,args|
147
- puts "I AM IN START"
148
-
149
- # If you have any errors, just raise them
150
- # raise "that command made no sense"
155
+ c.action do |global,options,args|
156
+ puts "I AM IN STATUS"
151
157
  end
152
158
  end
153
159
 
@@ -155,18 +161,16 @@ end
155
161
  # ----- pre/post --------------------------------------------------------------
156
162
 
157
163
  pre do |global,command,options,args|
158
- # Pre logic here
159
- # Return true to proceed; false to abort and not call the
160
- # chosen command
161
- # Use skips_pre before a command to skip this block
162
- # on that command only
164
+ options[:app_config] = AppConfig.load(global) unless command.nil? || command.name == :help
165
+ options[:templates] = TemplateConfig.load(global) unless command.nil? || command.name == :help
166
+
167
+ # Return true to proceed; false to abort and not call the chosen command
168
+ # Use skips_pre before a command to skip this block on that command only
163
169
  true
164
170
  end
165
171
 
166
172
  post do |global,command,options,args|
167
- # Post logic here
168
- # Use skips_post before a command to skip this
169
- # block on that command only
173
+ # Use skips_post before a command to skip this block on that command only
170
174
  end
171
175
 
172
176
  on_error do |exception|
@@ -0,0 +1,23 @@
1
+ require 'hash_deep_merge'
2
+
3
+ module Proctor
4
+ class AppConfig
5
+
6
+ def self.load(global_options = {:f => "Proctorfile"})
7
+ app_files = self.app_files(global_options).map {|f| AppFile.new(f)}
8
+ app_files.reduce({}) { |a,v| a.deep_merge(v.config_data) }
9
+ end
10
+
11
+ def self.app_files(global_options)
12
+ proctorfile = global_options[:f]
13
+ ["#{base_dir}/proctor/Proctorfile", "#{Dir.pwd}/#{proctorfile}", ]
14
+ end
15
+
16
+ private
17
+
18
+ def self.base_dir
19
+ File.expand_path(File.dirname(File.expand_path(__FILE__)) + '/../..')
20
+ end
21
+
22
+ end
23
+ end
@@ -1,5 +1,44 @@
1
+ require 'yaml'
2
+
1
3
  module Proctor
2
- module AppFile
4
+ class AppFile
5
+ attr_reader :config_data
6
+
7
+ def initialize(filename)
8
+ check_file_existence(filename)
9
+ @config_data = load_data(filename)
10
+ check_data_format(filename)
11
+ end
12
+
13
+ private
14
+
15
+ def load_data(file)
16
+ begin
17
+ @config_data = YAML.load_file(file)
18
+ rescue Psych::SyntaxError
19
+ error_msg = "invalid file format (expecting YAML - #{file})"
20
+ raise InvalidYamlException.new, error_msg
21
+ end
22
+ end
23
+
24
+ def check_data_format(file)
25
+ raise InvalidDataType, "expecting hash data (#{file})" unless @config_data.is_a?(Hash)
26
+ end
27
+
28
+ def check_file_existence(file)
29
+ error_msg = "missing app file (#{file})"
30
+ raise MissingFileException, error_msg unless File.exist?(file)
31
+ end
32
+
33
+ end
34
+
35
+ class MissingFileException < RuntimeError
36
+ end
37
+
38
+ class InvalidYamlException < RuntimeError
39
+ end
3
40
 
41
+ class InvalidDataType < RuntimeError
4
42
  end
43
+
5
44
  end
@@ -0,0 +1,39 @@
1
+ module Proctor
2
+
3
+ class CmdState
4
+
5
+ def self.app_files(global, options, args)
6
+ AppConfig.app_files(global)
7
+ end
8
+
9
+ def self.templates(global, options, args)
10
+ options[:templates].values.sort
11
+ end
12
+
13
+ def self.nodes(global, options, args)
14
+ options[:app_config]['nodes']
15
+ end
16
+
17
+ def self.node_names(global, options, args)
18
+ options[:app_config]['nodes'].keys
19
+ end
20
+
21
+ def self.services(global, options, args)
22
+ options[:app_config]['services']
23
+ end
24
+
25
+ def self.service_names(global, options, args)
26
+ options[:app_config]['services'].keys
27
+ end
28
+
29
+ def self.managers(global, options, args)
30
+ options[:app_config]['managers']
31
+ end
32
+
33
+ def self.manager_names(global, options, args)
34
+ options[:app_config]['managers'].keys
35
+ end
36
+
37
+ end
38
+
39
+ end
@@ -1,5 +1,11 @@
1
1
  module Proctor
2
- module Manager
2
+ class Manager
3
+ attr_reader :input_hash, :opts
4
+
5
+ def initialize(hash = {})
6
+ @input_hash = hash
7
+ end
3
8
 
4
9
  end
5
- end
10
+ end
11
+
@@ -0,0 +1,67 @@
1
+ require 'digest/md5'
2
+
3
+ module Proctor
4
+ module Presenter
5
+ class List
6
+
7
+ def initialize(global, options, args)
8
+ @global = global
9
+ @options = options
10
+ @args = args
11
+ end
12
+
13
+ def render
14
+ case @options[:type]
15
+ when "appfiles" then render_appfiles
16
+ when "managers" then render_managers
17
+ when "services" then render_services
18
+ when "templates" then render_templates
19
+ when "node" then render_node
20
+ when "all" then render_all
21
+ end
22
+ end
23
+
24
+ def handle(string)
25
+ Digest::MD5.hexdigest(string)[0..2]
26
+ end
27
+
28
+ def puts_handle(string)
29
+ puts "#{handle(string)}: #{string}"
30
+ end
31
+
32
+ def render_templates
33
+ puts "[Templates]"
34
+ CmdState.templates(@global, @options, @args).each {|v| puts_handle(v)}
35
+ end
36
+
37
+ def render_appfiles
38
+ puts "[AppFiles]"
39
+ CmdState.app_files(@global, @options, @args).each { |f| puts "#{handle(f)}: #{f}" }
40
+ end
41
+
42
+ def render_managers
43
+ puts "[Managers]"
44
+ CmdState.managers(@global, @options, @args).keys.each {|k| puts_handle(k)}
45
+ end
46
+
47
+ def render_services
48
+ puts "[Services]"
49
+ CmdState.services(@global, @options, @args).keys.each {|k| puts_handle(k)}
50
+ end
51
+
52
+ def render_node
53
+ puts "[Nodes]"
54
+ CmdState.nodes(@global, @options, @args).keys.each {|k| puts_handle(k)}
55
+ end
56
+
57
+ def render_all
58
+ render_templates
59
+ render_appfiles
60
+ render_managers
61
+ render_services
62
+ render_node
63
+ end
64
+
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,39 @@
1
+ module Proctor
2
+ module Presenter
3
+ class Render
4
+
5
+ def initialize(global, options, args)
6
+ @global = global
7
+ @options = options
8
+ @args = args
9
+ @role = args[0]
10
+ @manager = args[1]
11
+ check_for_valid_role(@role)
12
+ check_for_valid_manager(@manager)
13
+ end
14
+
15
+ def check_for_valid_role(role)
16
+ roles = CmdState.roles(@global, @options, @args) + ["manifest"]
17
+ unless roles.include? role
18
+ raise "Invalid ROLE (#{role}) - use one of [#{roles.join('|')}]"
19
+ end
20
+ end
21
+
22
+ def check_for_valid_manager(manager)
23
+ managers = CmdState.manager_names(@global, @options, @args)
24
+ unless managers.include? manager
25
+ raise "Invalid MANAGER (#{manager}) - use one of [#{managers.join('|')}]"
26
+ end
27
+ end
28
+
29
+ def render_template
30
+ puts "# Template #{@role} #{@manager}"
31
+ end
32
+
33
+ def render
34
+ render_template
35
+ end
36
+
37
+ end
38
+ end
39
+ end