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