proctor 0.0.2 → 0.0.3

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