servitude 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'servitude'
5
+ require 'socket'
6
+ require 'trollop'
7
+ require 'pry'
8
+
9
+ # The echo server accepts with CLI builds on the 2_echo_server example, addiing a command line interface.
10
+ #
11
+ # Note: Due to TcpServer#accept's implementation, the server is not currently gracefully shutting down as the trap of INT
12
+ # appears to never happen.
13
+ #
14
+ # The CLI has 4 sub-commands: start, stop, status and restart.
15
+ #
16
+ # Using the -i of --interactive switch on the start command will run the server attached in the terminal you execute the command
17
+ # in. Without the interactive switch, the server will run as a daemon. The start, stop, and status commands are used to control
18
+ # the daemonized process. If run in daemon mode, you can tail the log:
19
+ #
20
+ # $ tail -f tmp/echo-server.log
21
+ #
22
+ # Usage:
23
+ # Help:
24
+ # bundle exec examples/5_echo_server_with_cli_daemon_and_env_config -h
25
+ #
26
+ # Start (interactive):
27
+ # bundle exec examples/5_echo_server_with_cli_daemon_and_env_config start -i
28
+ #
29
+ # Notice the line in the log just under the start banner that states: (Resolved for env production) key2: value3. This is because
30
+ # The config file has an environment key in it with the value production. Start the server again overridin ghte environemnt on the cammand
31
+ # line:
32
+ # bundle exec examples/5_echo_server_with_cli_daemon_and_env_config start -i -d development
33
+ #
34
+ # Notice the line in the log just under the start banner that states: (Resolved for env development) key2: value2. The environment was
35
+ # successfully overridden on the caommand line and the value for key2 changed accordingly.
36
+ #
37
+ # Then use telnet to exercise the server:
38
+ # $ telnet localhost 1234
39
+ # Hello World!
40
+ # You said: Hello World!
41
+ # Connection closed by foreign host.
42
+ #
43
+ module EchoServer
44
+
45
+ include Servitude::Base
46
+
47
+ APP_FOLDER = 'echo-server'
48
+ VERSION = '1.0.0'
49
+
50
+ PROJECT_ROOT = File.expand_path( '../..', __FILE__ )
51
+
52
+ boot host_namespace: EchoServer,
53
+ app_id: 'echo-server-with-cli-and-env-config',
54
+ app_name: 'Echo Server With CLI And Env Config',
55
+ company: 'LFE',
56
+ use_config: true,
57
+ default_config_path: "#{PROJECT_ROOT}/config/5.conf",
58
+ default_log_path: "#{PROJECT_ROOT}/tmp/#{APP_FOLDER}.log",
59
+ default_pid_path: "#{PROJECT_ROOT}/tmp/#{APP_FOLDER}.pid",
60
+ default_thread_count: nil,
61
+ version_copyright: "v#{VERSION} \u00A9#{Time.now.year} LFE"
62
+
63
+ class Cli
64
+
65
+ include Servitude::Cli
66
+
67
+ end
68
+
69
+ class Server
70
+
71
+ include Servitude::Server
72
+
73
+ after_initialize do
74
+ @tcp_server = TCPServer.open( 'localhost', '1234' )
75
+ end
76
+
77
+ before_run do
78
+ info "(Resolved for env #{config.environment}) key2: #{config.for_env.key2}"
79
+ end
80
+
81
+ finalize do
82
+ info 'Shutting down ...'
83
+ end
84
+
85
+ def run
86
+ while client = tcp_server.accept
87
+ line = client.gets
88
+ info "Received '#{line.strip}'"
89
+ response = "You said: #{line.strip}"
90
+ client.puts response
91
+ info "Responded with '#{response}'"
92
+ info "Closing connection"
93
+ client.close
94
+ end
95
+ end
96
+
97
+ protected
98
+
99
+ def configuration_class
100
+ Servitude::EnvironmentConfiguration
101
+ end
102
+
103
+ def config_filters
104
+ %w(threads)
105
+ end
106
+
107
+ private
108
+
109
+ attr_reader :tcp_server
110
+
111
+ end
112
+ end
113
+
114
+ EchoServer::Cli.new( ARGV ).run
data/lib/servitude.rb CHANGED
@@ -3,16 +3,19 @@ require 'rainbow'
3
3
 
4
4
  module Servitude
5
5
 
6
- autoload :Actor, 'servitude/actor'
7
- autoload :Base, 'servitude/base'
8
- autoload :Cli, 'servitude/cli'
9
- autoload :Configuration, 'servitude/configuration'
10
- autoload :Daemon, 'servitude/daemon'
11
- autoload :Logging, 'servitude/logging'
12
- autoload :ServerLogging, 'servitude/server_logging'
13
- autoload :Server, 'servitude/server'
14
- autoload :ServerThreaded, 'servitude/server_threaded'
15
- autoload :SupervisionError, 'servitude/supervision_error'
6
+ autoload :Actor, 'servitude/actor'
7
+ autoload :Base, 'servitude/base'
8
+ autoload :Cli, 'servitude/cli'
9
+ autoload :ConfigHelper, 'servitude/config_helper'
10
+ autoload :Configuration, 'servitude/configuration'
11
+ autoload :Daemon, 'servitude/daemon'
12
+ autoload :EnvironmentConfiguration, 'servitude/environment_configuration'
13
+ autoload :Logging, 'servitude/logging'
14
+ autoload :PrettyPrint, 'servitude/pretty_print'
15
+ autoload :ServerLogging, 'servitude/server_logging'
16
+ autoload :Server, 'servitude/server'
17
+ autoload :ServerThreaded, 'servitude/server_threaded'
18
+ autoload :SupervisionError, 'servitude/supervision_error'
16
19
 
17
20
  INT = "INT"
18
21
  TERM = "TERM"
@@ -14,15 +14,16 @@ module Servitude
14
14
 
15
15
  module ClassMethods
16
16
 
17
- def boot( host_namespace:,
18
- app_id:,
19
- app_name:,
20
- company:,
21
- default_config_path:,
22
- default_log_path:,
23
- default_pid_path:,
24
- default_thread_count:,
25
- version_copyright:)
17
+ def boot( host_namespace: raise(ArgumentError, 'host_namespace keyword is required'),
18
+ app_id: raise(ArgumentError, 'app_id keyword is required'),
19
+ app_name: raise(ArgumentError, 'app_name keyword is required'),
20
+ company: raise(ArgumentError, 'company keyword is required'),
21
+ use_config: raise(ArgumentError, 'use_config keyword is required'),
22
+ default_config_path: raise(ArgumentError, 'default_config_path keyword is required'),
23
+ default_log_path: raise(ArgumentError, 'default_log_path keyword is required'),
24
+ default_pid_path: raise(ArgumentError, 'default_pid_path keyword is required'),
25
+ default_thread_count: raise(ArgumentError, 'default_thread_count keyword is required'),
26
+ version_copyright: raise(ArgumentError, 'version_copyright keyword is required') )
26
27
  Servitude::const_set :NS, host_namespace
27
28
 
28
29
  const_set :APP_ID, app_id
@@ -32,13 +33,14 @@ module Servitude
32
33
  const_set :DEFAULT_LOG_PATH, default_log_path
33
34
  const_set :DEFAULT_PID_PATH, default_pid_path
34
35
  const_set :DEFAULT_THREAD_COUNT, default_thread_count
36
+ const_set :USE_CONFIG, use_config
35
37
  const_set :VERSION_COPYRIGHT, version_copyright
36
38
 
37
39
  Servitude::boot_called = true
38
40
  end
39
41
 
40
42
  def configuration
41
- @configuration ||= Servitude::NS::Configuration.new
43
+ @configuration
42
44
  end
43
45
 
44
46
  def configuration=( configuration )
data/lib/servitude/cli.rb CHANGED
@@ -52,6 +52,7 @@ options:
52
52
  when "restart"
53
53
  Trollop::options do
54
54
  opt :config, "The path for the config file", :type => String, :short => '-c', :default => Servitude::NS::DEFAULT_CONFIG_PATH
55
+ opt :environment, "The log level", :type => String, :short => '-e'
55
56
  opt :log_level, "The log level", :type => String, :default => 'info', :short => '-o'
56
57
  opt :log, "The path for the log file", :type => String, :short => '-l', :default => Servitude::NS::DEFAULT_LOG_PATH
57
58
  opt :pid, "The path for the PID file", :type => String, :default => Servitude::NS::DEFAULT_PID_PATH
@@ -60,6 +61,7 @@ options:
60
61
  when "start"
61
62
  Trollop::options do
62
63
  opt :config, "The path for the config file", :type => String, :short => '-c', :default => Servitude::NS::DEFAULT_CONFIG_PATH
64
+ opt :environment, "The log level", :type => String, :short => '-e'
63
65
  opt :interactive, "Execute the server in interactive mode", :short => '-i'
64
66
  opt :log_level, "The log level", :type => String, :default => 'info', :short => '-o'
65
67
  opt :log, "The path for the log file", :type => String, :short => '-l', :default => Servitude::NS::DEFAULT_LOG_PATH
@@ -91,9 +93,6 @@ options:
91
93
  end
92
94
  end
93
95
 
94
- def self.commands( &block )
95
- end
96
-
97
96
  def run
98
97
  send( cmd )
99
98
  end
@@ -109,17 +108,17 @@ options:
109
108
  end
110
109
 
111
110
  def start_interactive
112
- server = Servitude::NS::Server.new( options.merge( log: nil ))
111
+ server = Servitude::NS::Server.new( options.merge( use_config: Servitude::NS::USE_CONFIG, log: 'STDOUT' ))
113
112
  server.start
114
113
  end
115
114
 
116
115
  def start_daemon
117
- server = Servitude::Daemon.new( options )
116
+ server = Servitude::Daemon.new( options.merge( use_config: Servitude::NS::USE_CONFIG ))
118
117
  server.start
119
118
  end
120
119
 
121
120
  def stop
122
- server = Servitude::Daemon.new( options )
121
+ server = Servitude::Daemon.new( options.merge( use_config: Servitude::NS::USE_CONFIG ))
123
122
  server.stop
124
123
  end
125
124
 
@@ -129,7 +128,7 @@ options:
129
128
  end
130
129
 
131
130
  def status
132
- Servitude::Daemon.new( options ).status
131
+ Servitude::Daemon.new( options.merge( use_config: Servitude::NS::USE_CONFIG )).status
133
132
  end
134
133
 
135
134
  end
@@ -0,0 +1,11 @@
1
+ require 'celluloid/autostart'
2
+
3
+ module Servitude
4
+ module ConfigHelper
5
+
6
+ def config
7
+ Servitude::NS::configuration
8
+ end
9
+
10
+ end
11
+ end
@@ -1,58 +1,34 @@
1
- module Servitude
2
- module Configuration
3
-
4
- def self.included( base )
5
- base.extend ClassMethods
6
-
7
- base.class_eval do
8
- class << self
9
- attr_accessor :attributes,
10
- :configured
11
- end
12
- end
13
- end
14
-
15
- module ClassMethods
16
-
17
- def configurations( *attrs )
18
- raise 'Already configured: cannot call configurations more than once' if self.configured
1
+ require 'delegate'
2
+ require 'hashie'
3
+ require 'oj'
19
4
 
20
- self.attributes = attrs.map { |attr| Array( attr ).first.to_s }
21
-
22
- class_eval do
23
- attr_accessor( *self.attributes )
24
- end
25
-
26
- attrs.select { |attr| attr.is_a?( Array ) }.
27
- each do |k, v|
28
-
29
- define_method k do
30
- instance_variable_get( "@#{k}" ) ||
31
- instance_variable_set( "@#{k}", v )
32
- end
5
+ module Servitude
6
+ class Configuration < SimpleDelegator
33
7
 
34
- end
8
+ def initialize( options={} )
9
+ options.reject! { |k,v| v.nil? }
35
10
 
36
- self.configured = true
11
+ if options[:use_config]
12
+ @_config = Hashie::Mash.new( file_options( options[:config] ))
13
+ _config.merge!( options )
14
+ else
15
+ @_config = Hashie::Mash.new( options )
37
16
  end
38
17
 
39
- def from_file( file_path )
40
- unless File.exists?( file_path )
41
- raise "Configuration file #{file_path} does not exist"
42
- end
18
+ super _config
19
+ end
43
20
 
44
- options = Oj.load( File.read( file_path ))
45
- Servitude::NS::configuration = Servitude::NS::Configuration.new
21
+ protected
46
22
 
47
- attributes.each do |c|
48
- if options[c]
49
- Servitude::NS::configuration.send( :"#{c}=", options[c] )
50
- end
51
- end
23
+ attr_reader :_config
52
24
 
53
- options[:config_loaded] = true
25
+ def file_options( file_path )
26
+ unless File.exists?( file_path )
27
+ raise "Configuration file #{file_path} does not exist"
54
28
  end
55
29
 
30
+ Oj.load( File.read( file_path ))
56
31
  end
32
+
57
33
  end
58
34
  end
@@ -0,0 +1,13 @@
1
+ require 'delegate'
2
+ require 'hashie'
3
+ require 'oj'
4
+
5
+ module Servitude
6
+ class EnvironmentConfiguration < Servitude::Configuration
7
+
8
+ def for_env
9
+ _config.send( _config.environment )
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,36 @@
1
+ require 'pry'
2
+ module Servitude
3
+ module PrettyPrint
4
+
5
+ def self.configuration_lines( config, pre='', filters=[] )
6
+ return [] if config.nil? || config.empty?
7
+
8
+ formatted = format_configuration( config )
9
+
10
+ formatted.map do |line_parts|
11
+ if !filters.nil? && !filters.empty? && filters.include?( line_parts.first )
12
+ nil
13
+ else
14
+ [pre, line_parts.join( ': ' )].join
15
+ end
16
+ end.reject( &:nil? )
17
+ end
18
+
19
+ def self.format_configuration( config, path=[], result=[] )
20
+ config.each do |element|
21
+ key, value = *element
22
+ cur_path = path + [key]
23
+ if value.is_a?( Hash )
24
+ format_configuration( value, cur_path, result )
25
+ elsif value.is_a?( Array )
26
+ result << [cur_path.map( &:to_s ).join( '.' ), value.inspect]
27
+ else
28
+ result << [cur_path.map( &:to_s ).join( '.' ), value]
29
+ end
30
+ end
31
+
32
+ result
33
+ end
34
+
35
+ end
36
+ end
@@ -6,38 +6,42 @@ module Servitude
6
6
 
7
7
  def self.included( base )
8
8
  base.class_eval do
9
+ include ConfigHelper
9
10
  include Logging
10
11
  include ServerLogging
11
12
  include Hooks
12
13
 
13
14
  define_hook :after_initialize,
14
15
  :before_initialize,
16
+ :before_run,
15
17
  :before_sleep,
16
18
  :finalize
17
19
 
18
- attr_reader :options
20
+ attr_reader :cli_options
19
21
  end
20
22
  end
21
23
 
22
- def initialize( options={} )
24
+ def initialize( cli_options={} )
23
25
  unless Servitude.boot_called
24
26
  raise 'You must call boot before starting server'
25
27
  end
26
28
 
27
- @options = options
29
+ @cli_options = cli_options
28
30
 
29
31
  run_hook :before_initialize
32
+ initialize_config
30
33
  initialize_loggers
31
34
  run_hook :after_initialize
32
35
  end
33
36
 
34
37
  def start
35
38
  log_startup
36
- run
37
39
 
38
40
  trap( INT ) { stop }
39
41
  trap( TERM ) { stop }
40
42
 
43
+ run_hook :before_run
44
+ run
41
45
  run_hook :before_sleep
42
46
  sleep
43
47
  end
@@ -48,6 +52,14 @@ module Servitude
48
52
  raise NotImplementedError
49
53
  end
50
54
 
55
+ def initialize_config
56
+ Servitude::NS::configuration = configuration_class.new( cli_options )
57
+ end
58
+
59
+ def configuration_class
60
+ Servitude::Configuration
61
+ end
62
+
51
63
  private
52
64
 
53
65
  def finalize