servitude 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +2 -0
- data/README.md +129 -13
- data/config/4.conf +6 -0
- data/config/5.conf +10 -0
- data/examples/1_simple_server +7 -4
- data/examples/2_echo_server +7 -5
- data/examples/3_echo_server_with_cli_and_daemon +98 -0
- data/examples/4_echo_server_with_cli_daemon_and_file_config +98 -0
- data/examples/5_echo_server_with_cli_daemon_and_env_config +114 -0
- data/lib/servitude.rb +13 -10
- data/lib/servitude/base.rb +12 -10
- data/lib/servitude/cli.rb +6 -7
- data/lib/servitude/config_helper.rb +11 -0
- data/lib/servitude/configuration.rb +21 -45
- data/lib/servitude/environment_configuration.rb +13 -0
- data/lib/servitude/pretty_print.rb +36 -0
- data/lib/servitude/server.rb +16 -4
- data/lib/servitude/server_logging.rb +44 -34
- data/lib/servitude/version.rb +1 -1
- data/servitude.gemspec +2 -0
- data/spec/servitude/pretty_print_spec.rb +71 -0
- data/spec/spec_helper.rb +89 -0
- metadata +45 -3
@@ -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,
|
7
|
-
autoload :Base,
|
8
|
-
autoload :Cli,
|
9
|
-
autoload :
|
10
|
-
autoload :
|
11
|
-
autoload :
|
12
|
-
autoload :
|
13
|
-
autoload :
|
14
|
-
autoload :
|
15
|
-
autoload :
|
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"
|
data/lib/servitude/base.rb
CHANGED
@@ -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
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
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:
|
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
|
@@ -1,58 +1,34 @@
|
|
1
|
-
|
2
|
-
|
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
|
-
|
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
|
-
|
8
|
+
def initialize( options={} )
|
9
|
+
options.reject! { |k,v| v.nil? }
|
35
10
|
|
36
|
-
|
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
|
-
|
40
|
-
|
41
|
-
raise "Configuration file #{file_path} does not exist"
|
42
|
-
end
|
18
|
+
super _config
|
19
|
+
end
|
43
20
|
|
44
|
-
|
45
|
-
Servitude::NS::configuration = Servitude::NS::Configuration.new
|
21
|
+
protected
|
46
22
|
|
47
|
-
|
48
|
-
if options[c]
|
49
|
-
Servitude::NS::configuration.send( :"#{c}=", options[c] )
|
50
|
-
end
|
51
|
-
end
|
23
|
+
attr_reader :_config
|
52
24
|
|
53
|
-
|
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,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
|
data/lib/servitude/server.rb
CHANGED
@@ -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 :
|
20
|
+
attr_reader :cli_options
|
19
21
|
end
|
20
22
|
end
|
21
23
|
|
22
|
-
def initialize(
|
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
|
-
@
|
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
|