servitude 0.1.0 → 0.2.0

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