right_support 0.8.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.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 RightScale, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ 'Software'), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,66 @@
1
+ RightSupport is a library of reusable, unit-tested Ruby code that RightScale has found broadly useful.
2
+
3
+ == What Does It Do?
4
+
5
+ === Logging
6
+
7
+ SystemLogger is a rewrite of the seattle.rb SyslogLogger class that features the following improvements:
8
+ * Contains several bugfixes vs. SyslogLogger 1.4.0
9
+ * Inherits from standard Ruby Logger class for guaranteed compatibility
10
+ * Can be configured with options about how to handle newlines, ANSI escape codes, etc
11
+
12
+ @logger = SystemLogger.new('my_cool_app', :split=>true, :color=>false)
13
+ @logger.info "Hello world\nThis will appear on separate lines\nand without \e[33;0mbeautiful colors"
14
+
15
+ CustomLogger is a Rack middleware that allows a Rack app to use any log sink it wishes. The
16
+ stock Rack logger middleware is inflexible and gives the end user no control over which logger is used
17
+ or where the log entries go.
18
+
19
+ require 'right_support/rack/custom_logger'
20
+
21
+ my_logger = MyAwesomeLogger.new
22
+ use RightSupport::Rack::CustomLogger, Logger::INFO, my_logger
23
+
24
+ == Input Validation
25
+
26
+ The Validation module contains several format-checkers that can be used to validate
27
+ your web app's models before saving, check for preconditions in your controllers, and
28
+ so forth.
29
+
30
+ You can use it as a mixin by including the appropriate child module of
31
+ RightSupport::Validation, but you really don't want to do that, do you? Instead, you
32
+ want to call the module methods of RightSupport::Validation, which contains all of
33
+ the same mixin methods.
34
+
35
+ the_key = STDIN.read
36
+ raise ArgumentError unless RightSupport::Validation.ssh_public_key?(the_key)
37
+
38
+ == Request Balancing
39
+
40
+ The RequestBalancer class will randomly choose endpoints for a network request,
41
+ which lets you perform easy client-side load balancing:
42
+
43
+ include RightSupport::Net
44
+
45
+ urls = ['http://localhost', 'http://otherhost']
46
+ RequestBalancer.request(urls, :fatal=>RestClient::ResourceNotFound) do |url|
47
+ REST.get(url)
48
+ end
49
+
50
+ The balancer will keep trying requests until one of them succeeds without throwing
51
+ any exceptions. (NB: a nil return value counts as success!!)
52
+
53
+ == HTTP REST Client
54
+
55
+ We provide a very thin wrapper around the rest-client gem that enables simple but
56
+ robust rest requests with a timeout, headers, etc.
57
+
58
+ The RightSupport::Net::REST module is interface-compatible with the RestClient
59
+ module, but allows an optional timeout to be specified as an extra parameter.
60
+
61
+ # Default timeout is 5 seconds
62
+ RightSupport::Net::REST.get('http://localhost')
63
+
64
+ # Make sure the REST request fails after 1 second so we can report an error
65
+ # and move on!
66
+ RightSupport::Net::REST.get('http://localhost', {'X-Hello'=>'hi!'}, 1)
@@ -0,0 +1,122 @@
1
+ #
2
+ # Copyright (c) 2011 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ require 'logger'
24
+
25
+ module RightSupport
26
+ # A logger than encapsulates an underlying Logger object and filters log entries
27
+ # before they are passed to the underlying Logger. Can be used to for various log-
28
+ # processing tasks such as filtering sensitive data or tagging log lines with a
29
+ # context marker.
30
+ class FilterLogger < Logger
31
+ # Initialize a new instance of this class.
32
+ #
33
+ # === Parameters
34
+ # actual_logger(Logger):: The actual, underlying Logger object
35
+ #
36
+ def initialize(actual_logger)
37
+ @actual_logger = actual_logger
38
+
39
+ end
40
+
41
+ # Add a log line, filtering the severity and message before calling through
42
+ # to the underlying logger's #add method.
43
+ #
44
+ # === Parameters
45
+ # severity(Integer):: one of the Logger severity constants
46
+ # message(String):: the message to log, or nil
47
+ # progname(String):: the program name, or nil
48
+ #
49
+ # === Block
50
+ # If message == nil and a block is given, yields to the block in order to
51
+ # capture the log message. This matches the behavior of Logger, but ensures
52
+ # the severity and message are still filtered.
53
+ #
54
+ # === Return
55
+ # the result of the underlying logger's #add
56
+ def add(severity, message = nil, progname = nil, &block)
57
+ severity ||= UNKNOWN
58
+ return true if severity < level
59
+
60
+ if message.nil?
61
+ if block_given?
62
+ message = yield
63
+ else
64
+ message = progname
65
+ end
66
+ end
67
+
68
+ severity, message = filter(severity, message)
69
+ return @actual_logger.add(severity, message) if message
70
+ end
71
+
72
+ # Proxies to the encapsulated Logger object. See Logger#<< for info.
73
+ def <<(msg)
74
+ @actual_logger << msg
75
+ end
76
+
77
+ # Proxies to the encapsulated Logger object. See Logger#close for info.
78
+ def close
79
+ @actual_logger.close
80
+ end
81
+
82
+ # Proxies to the encapsulated Logger object. See Logger#level for info.
83
+ def level
84
+ @actual_logger.level
85
+ end
86
+
87
+ # Proxies to the encapsulated Logger object. See Logger#level= for info.
88
+ def level=(new_level)
89
+ @actual_logger.level = new_level
90
+ end
91
+
92
+ # Proxies to the encapsulated Logger object. See Logger#debug? for info.
93
+ def debug?; @actual_logger.debug?; end
94
+
95
+ # Proxies to the encapsulated Logger object. See Logger#info? for info.
96
+ def info?; @actual_logger.info?; end
97
+
98
+ # Proxies to the encapsulated Logger object. See Logger#warn? for info.
99
+ def warn?; @actual_logger.warn?; end
100
+
101
+ # Proxies to the encapsulated Logger object. See Logger#error? for info.
102
+ def error?; @actual_logger.error?; end
103
+
104
+ # Proxies to the encapsulated Logger object. See Logger#fatal? for info.
105
+ def fatal?; @actual_logger.fatal?; end
106
+
107
+ protected
108
+
109
+ # Filter a log line, transforming its severity and/or message before it is
110
+ # passed to the underlying logger.
111
+ #
112
+ # === Parameters
113
+ # severity(Integer):: one of the severity constants defined by Logger
114
+ # messgae(String):: the log message
115
+ #
116
+ # === Return
117
+ # Returns a pair consisting of the filtered [severity, message].
118
+ def filter(severity, message)
119
+ return [severity, message]
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,30 @@
1
+ module RightSupport
2
+ module KernelExtensions
3
+ # Attempt to require one or more source files; if the require succeeds (or
4
+ # if the files have already been successfully required), yield to the block.
5
+ #
6
+ # This method is useful to conditionally define code depending on the availability
7
+ # of gems or standard-library source files.
8
+ #
9
+ # === Parameters
10
+ # Uses a parameters glob to pass all of its parameters transparently through to
11
+ # Kernel#require.
12
+ #
13
+ # === Block
14
+ # The block will be called if the require succeeds (if it does not raise LoadError).
15
+ #
16
+ # === Return
17
+ # Preserves the return value of Kernel#require (generally either true or false).
18
+ def if_require_succeeds(*args)
19
+ result = require(*args)
20
+ yield if block_given?
21
+ return result
22
+ rescue LoadError => e
23
+ return false
24
+ end
25
+ end
26
+ end
27
+
28
+ class Object
29
+ include RightSupport::KernelExtensions
30
+ end
@@ -0,0 +1,47 @@
1
+ module RightSupport::Net
2
+ class NoResponse < Exception; end
3
+
4
+ # Utility class that allows network requests to be randomly distributed across
5
+ # a set of network endpoints. Generally used for REST requests by passing an
6
+ # Array of HTTP service endpoint URLs.
7
+ #
8
+ # The balancer does not actually perform requests by itself, which makes this
9
+ # class usable for various network protocols, and potentially even for non-
10
+ # networking purposes. The block does all the work; the balancer merely selects
11
+ # a random request endpoint to pass to the block.
12
+ class RequestBalancer
13
+ def self.request(endpoints, options={}, &block)
14
+ new(endpoints, options).request(&block)
15
+ end
16
+
17
+ def initialize(endpoints, options={})
18
+ raise ArgumentError, "Must specify at least one endpoint" unless endpoints && !endpoints.empty?
19
+ @endpoints = endpoints.shuffle
20
+ @options = options.dup
21
+ end
22
+
23
+ def request
24
+ raise ArgumentError, "Must call this method with a block" unless block_given?
25
+
26
+ exception = nil
27
+ result = nil
28
+
29
+ @endpoints.each do |host|
30
+ begin
31
+ result = yield(host)
32
+ break unless result.nil?
33
+ rescue Exception => e
34
+ fatal = @options[:fatal]
35
+ safe = @options[:safe]
36
+ raise e if (fatal && e.kind_of?(fatal)) && !(safe && e.kind_of?(safe))
37
+ exception = e
38
+ end
39
+ end
40
+
41
+ return result if result
42
+ raise exception if exception
43
+ raise NoResponse, "Tried all URLs with neither result nor exception!"
44
+ end
45
+ end # RequestBalancer
46
+
47
+ end # RightScale
@@ -0,0 +1,43 @@
1
+ module RightSupport::Net
2
+ if_require_succeeds('restclient') do
3
+ HAS_REST_CLIENT = true
4
+ end
5
+
6
+ class NoProvider < Exception; end
7
+
8
+ #
9
+ # A wrapper for the rest-client gem that provides timeouts and other
10
+ # useful features while preserving the simplicity and ease of use of
11
+ # RestClient's simple, static (module-level) interface.
12
+ #
13
+ module REST
14
+ DEFAULT_TIMEOUT = 5
15
+
16
+ def self.get(url, headers={}, timeout=DEFAULT_TIMEOUT, &block)
17
+ request(:method=>:get, :url=>url, :timeout=>timeout, :headers=>headers, &block)
18
+ end
19
+
20
+ def self.post(url, payload, headers={}, timeout=DEFAULT_TIMEOUT, &block)
21
+ request(:method=>:post, :url=>url, :payload=>payload,
22
+ :timeout=>timeout, :headers=>headers, &block)
23
+ end
24
+
25
+ def self.put(url, payload, headers={}, timeout=DEFAULT_TIMEOUT, &block)
26
+ request(:method=>:put, :url=>url, :payload=>payload,
27
+ :timeout=>timeout, :headers=>headers, &block)
28
+ end
29
+
30
+ def self.delete(url, headers={}, timeout=DEFAULT_TIMEOUT, &block)
31
+ request(:method=>:delete, :url=>url, :timeout=>timeout, :headers=>headers, &block)
32
+ end
33
+
34
+ def self.request(options, &block)
35
+ if HAS_REST_CLIENT
36
+ RestClient::Request.execute(options, &block)
37
+ else
38
+ raise NoProvider, "Cannot find a suitable HTTP client library"
39
+ end
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,34 @@
1
+ #
2
+ # Copyright (c) 2011 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ module RightSupport
24
+ #
25
+ # A namespace for useful networking tools.
26
+ #
27
+ module Net
28
+
29
+ end
30
+ end
31
+
32
+ Dir[File.expand_path('../net/*.rb', __FILE__)].each do |filename|
33
+ require filename
34
+ end
@@ -0,0 +1,62 @@
1
+ #
2
+ # Copyright (c) 2011 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ require 'logger'
24
+
25
+ module RightSupport
26
+ module Rack
27
+ # A Rack middleware that allows an arbitrary object to be used as the Rack logger.
28
+ # This is more flexible than Rack's built-in Logger middleware, which always logs
29
+ # to a file-based Logger and doesn't allow you to control anything other than the
30
+ # filename.
31
+ class CustomLogger
32
+ # Initialize an instance of the middleware.
33
+ #
34
+ # === Parameters
35
+ # app(Object):: the inner application or middleware layer; must respond to #call
36
+ # level(Integer):: one of the Logger constants: DEBUG, INFO, WARN, ERROR, FATAL
37
+ # logger(Logger):: (optional) the Logger object to use, if other than default
38
+ #
39
+ def initialize(app, level = ::Logger::INFO, logger = nil)
40
+ @app, @level = app, level
41
+
42
+ logger ||= ::Logger.new(env['rack.errors'])
43
+ logger.level = @level
44
+ @logger = logger
45
+ end
46
+
47
+ # Add a logger to the Rack environment and call the next middleware.
48
+ #
49
+ # === Parameters
50
+ # env(Hash):: the Rack environment
51
+ #
52
+ # === Return
53
+ # always returns whatever value is returned by the next layer of middleware
54
+ def call(env)
55
+ env['rack.logger'] = @logger
56
+ return @app.call(env)
57
+ ensure
58
+ @logger.close
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,175 @@
1
+ #
2
+ # Copyright (c) 2011 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ require 'logger'
24
+
25
+ module RightSupport
26
+ if_require_succeeds('syslog') do
27
+ # A logger that forwards log entries to the Unix syslog facility, but complies
28
+ # with the interface of the Ruby Logger object and faithfully translates log
29
+ # severities and other concepts. Provides optional cleanup/filtering in order
30
+ # to keep the syslog from having weird characters or being susceptible to log
31
+ # forgery.
32
+ class SystemLogger < Logger
33
+ LOGGER_LEVELS = {
34
+ UNKNOWN => :alert,
35
+ FATAL => :err,
36
+ ERROR => :warning,
37
+ WARN => :notice,
38
+ INFO => :info,
39
+ DEBUG => :debug
40
+ }
41
+
42
+ SYSLOG_LEVELS = LOGGER_LEVELS.invert
43
+ DEFAULT_SYSLOG_LEVEL = :alert
44
+
45
+ DEFAULT_OPTIONS = {:split=>false, :color=>false}
46
+
47
+ @@syslog = nil
48
+
49
+ # Initialize this process's syslog facilities and construct a new syslog
50
+ # logger object.
51
+ #
52
+ # === Parameters
53
+ # program_name(String):: the syslog program name, 'ruby' by default
54
+ # options(Hash):: (optional) configuration options to use, see below
55
+ #
56
+ # === Options
57
+ # :facility:: the syslog facility to use for messages, 'local0' by default
58
+ # :split(true|false):: if true, splits multi-line messages into separate syslog entries
59
+ # :color(true|false):: if true, passes ANSI escape sequences through to syslog
60
+ #
61
+ def initialize(program_name='ruby', options={})
62
+ @options = DEFAULT_OPTIONS.merge(options)
63
+ @level = Logger::DEBUG
64
+
65
+ facility = options[:facility] || 'local0'
66
+ fac_map = {'user'=>8}
67
+ (0..7).each { |i| fac_map['local'+i.to_s] = 128+8*i }
68
+ @@syslog ||= Syslog.open(program_name, nil, fac_map[facility.to_s])
69
+ end
70
+
71
+ # Log a message if the given severity is high enough. This is the generic
72
+ # logging method. Users will be more inclined to use #debug, #info, #warn,
73
+ # #error, and #fatal.
74
+ #
75
+ # === Parameters
76
+ # severity(Integer):: one of the severity constants defined by Logger
77
+ # message(Object):: the message to be logged
78
+ # progname(String):: ignored, the program name is fixed at initialization
79
+ #
80
+ # === Block
81
+ # If message is nil and a block is supplied, this method will yield to
82
+ # obtain the log message.
83
+ #
84
+ # === Return
85
+ # true:: always returns true
86
+ #
87
+ def add(severity, message = nil, progname = nil, &block)
88
+ severity ||= UNKNOWN
89
+ if @@syslog.nil? or severity < @level
90
+ return true
91
+ end
92
+
93
+ progname ||= @progname
94
+
95
+ if message.nil?
96
+ if block_given?
97
+ message = yield
98
+ else
99
+ message = progname
100
+ progname = @progname
101
+ end
102
+ end
103
+
104
+ parts = clean(message)
105
+ parts.each { |part| emit_syslog(severity, part) }
106
+ return true
107
+ end
108
+
109
+ # Emit a log entry at INFO severity.
110
+ #
111
+ # === Parameters
112
+ # msg(Object):: the message to log
113
+ #
114
+ # === Return
115
+ # true:: always returns true
116
+ #
117
+ def <<(msg)
118
+ info(msg)
119
+ end
120
+
121
+ # Do nothing. This method is provided for Logger interface compatibility.
122
+ #
123
+ # === Return
124
+ # true:: always returns true
125
+ #
126
+ def close
127
+ return true
128
+ end
129
+
130
+ private
131
+
132
+ # Call the syslog function to emit a syslog entry.
133
+ #
134
+ # === Parameters
135
+ # severity(Integer):: one of the Logger severity constants
136
+ # message(String):: the log message
137
+ #
138
+ # === Return
139
+ # true:: always returns true
140
+ def emit_syslog(severity, message)
141
+ level = SYSLOG_LEVELS[severity] || DEFAULT_SYSLOG_LEVEL
142
+ @@syslog.send(level, message)
143
+ return true
144
+ end
145
+
146
+ # Perform cleanup, output escaping and splitting on message.
147
+ # The operations that it performs can vary, depending on the
148
+ # options that were passed to this logger at initialization
149
+ # time.
150
+ #
151
+ # === Parameters
152
+ # message(String):: raw log message
153
+ #
154
+ # === Return
155
+ # log_lines([String]):: an array of String messages that should be logged separately to syslog
156
+ def clean(message)
157
+ message = message.to_s.dup
158
+ message.strip!
159
+ message.gsub!(/%/, '%%') # syslog(3) freaks on % (printf)
160
+
161
+ unless @options[:color]
162
+ message.gsub!(/\e\[[^m]*m/, '') # remove useless ansi color codes
163
+ end
164
+
165
+ if @options[:split]
166
+ bits = message.split(/[\n\r]+/)
167
+ else
168
+ bits = [message]
169
+ end
170
+
171
+ return bits
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,72 @@
1
+ #
2
+ # Copyright (c) 2011 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ require 'logger'
24
+
25
+ module RightSupport
26
+ # A logger that prepends a tag to every message that is emitted. Can be used to
27
+ # correlate logs with a Web session ID, transaction ID or other context.
28
+ #
29
+ # The user of this logger is responsible for calling #tag= to set the tag as
30
+ # appropriate, e.g. in a Web request around-filter.
31
+ #
32
+ # This logger uses thread-local storage (TLS) to provide tagging on a per-thread
33
+ # basis; however, it does not account for EventMachine, neverblock, the use of
34
+ # Ruby fibers, or any other phenomenon that can "hijack" a thread's call stack.
35
+ #
36
+ class TagLogger < FilterLogger
37
+ # Prepend the current tag to the log message; return the same severity and
38
+ # the modified message.
39
+ #
40
+ # === Parameters
41
+ # severity(Integer):: one of the severity constants defined by Logger
42
+ # messgae(String):: the log message
43
+ #
44
+ # === Return
45
+ # Returns a pair consisting of the filtered [severity, message].
46
+ #
47
+ def filter(severity, message)
48
+ @tls_id ||= "tag_logger_#{self.object_id}"
49
+ tag = Thread.current[@tls_id] || ''
50
+ if tag
51
+ return [severity, tag + message]
52
+ else
53
+ return [severity, message]
54
+ end
55
+ end
56
+
57
+ attr_reader :tag
58
+
59
+ # Set the tag for this logger.
60
+ #
61
+ # === Parameters
62
+ # tag(String|nil):: the new tag, or nil to remove the tag
63
+ #
64
+ # === Return
65
+ # String:: returns the new tag
66
+ def tag=(tag)
67
+ @tag = tag
68
+ @tls_id ||= "tag_logger_#{self.object_id}"
69
+ Thread.current[@tls_id] = @tag
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,90 @@
1
+ #
2
+ # Copyright (c) 2011 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ require 'openssl'
24
+
25
+ module RightSupport::Validation
26
+ # Validation methods pertaining to OpenSSL cryptography, e.g. various
27
+ # widely-used key formats and encoding/envelope formats.
28
+ module OpenSSL
29
+ # Determine whether a string is a PEM-encoded public or private key.
30
+ # Does not determine whether the key is valid, only that it is well-formed.
31
+ #
32
+ # === Parameters
33
+ # key_material(String):: the putative key material
34
+ #
35
+ # === Return
36
+ # If the key is well-formed, return the OpenSSL class that can be used
37
+ # to process the key material (e.g. OpenSSL::PKey::RSA). Otherwise, return
38
+ # false.
39
+ def pem_key?(key_material)
40
+ return false if key_material.nil? || key_material.empty?
41
+ m = /BEGIN ([A-Z]+) (PUBLIC|PRIVATE) KEY/.match(key_material)
42
+ return false unless m
43
+ case m[1]
44
+ when 'DSA' then return ::OpenSSL::PKey::DSA
45
+ when 'RSA' then return ::OpenSSL::PKey::RSA
46
+ else return false
47
+ end
48
+
49
+ end
50
+
51
+ # Determine whether a string is a valid PEM-encoded private key.
52
+ # Actually parses the key to prove validity as well as well-formedness.
53
+ # If the key is passphrase-protected, the passphrase is required in
54
+ # order to decrypt it; am incorrect passphrase will result in the key
55
+ # being recognized as not a valid key!
56
+ #
57
+ # === Parameters
58
+ # key_material(String):: the putative key material
59
+ # passphrase(String):: the encryption passphrase, if needed
60
+ #
61
+ # === Return
62
+ # If the key is well-formed and valid, return true. Otherwise, return false.
63
+ #
64
+ def pem_private_key?(key_material, passphrase=nil)
65
+ alg = pem_key?(key_material)
66
+ return false unless alg
67
+ key = alg.new(key_material, passphrase || 'dummy passphrase, should never work')
68
+ return key.private?
69
+ rescue ::OpenSSL::PKey::PKeyError, NotImplementedError
70
+ return false
71
+ end
72
+
73
+ # Determine whether a string is a valid PEM-encoded public key.
74
+ # Actually parses the key to prove validity as well as well-formedness.
75
+ #
76
+ # === Parameters
77
+ # key_material(String):: the putative key material
78
+ #
79
+ # === Return
80
+ # If the key is well-formed and valid, return true. Otherwise, return false.
81
+ def pem_public_key?(key_material)
82
+ alg = pem_key?(key_material)
83
+ return false unless alg
84
+ key = alg.new(key_material)
85
+ return key.public?
86
+ rescue ::OpenSSL::PKey::PKeyError, NotImplementedError
87
+ return false
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,70 @@
1
+ #
2
+ # Copyright (c) 2011 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ if_require_succeeds('net/ssh') do
24
+ module RightSupport::Validation
25
+ # Validation methods pertaining to the Secure Shell (SSH) protocol.
26
+ module SSH
27
+ # Determine whether a string is a valid PEM-encoded private key.
28
+ # Actually parses the key to prove validity as well as well-formedness.
29
+ # Relies on the OpenSSL Validation module to parse the private key
30
+ # since PEM is a standard non-SSH-specific key format.
31
+ #
32
+ # === Parameters
33
+ # key_material(String):: the putative key material
34
+ # passphrase(String):: the encryption passphrase, if needed
35
+ #
36
+ # === Return
37
+ # If the key is well-formed and valid, return true. Otherwise, return false.
38
+ def ssh_private_key?(key_material, passphrase=nil)
39
+ return RightSupport::Validation.pem_private_key?(key_material, passphrase)
40
+ end
41
+
42
+ # Determine whether a string is a valid public key in SSH public-key
43
+ # notation as might be found in an SSH authorized_keys file.
44
+ #
45
+ # However, authorized-key options are not allowed as they would be in an
46
+ # actual line of the authorized_keys file. The caller is responsible for
47
+ # stripping out any options. The string can consist of the following three
48
+ # whitespace-separated fields:
49
+ # * algorithm (e.g. "ssh-rsa")
50
+ # * key material (base64-encoded blob)
51
+ # * comments (e.g. "user@localhost"); optional
52
+ #
53
+ # This method actually parses the public key to prove validity as well as
54
+ # well-formedness.
55
+ #
56
+ # === Parameters
57
+ # key_material(String):: the putative key material
58
+ #
59
+ # === Return
60
+ # If the key is well-formed and valid, return true. Otherwise, return false.
61
+ def ssh_public_key?(key_material)
62
+ return false if key_material.nil? || key_material.empty?
63
+ ::Net::SSH::KeyFactory.load_data_public_key(key_material)
64
+ return true
65
+ rescue ::Net::SSH::Exception, ::OpenSSL::PKey::PKeyError, NotImplementedError
66
+ return false
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,45 @@
1
+ #
2
+ # Copyright (c) 2011 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ module RightSupport
24
+ # The Validation module acts as a namespace for various submodules that provide
25
+ # validation functions. These submodules can be mixed into classes in order to
26
+ # add validation logic.
27
+ #
28
+ # As a convenience, to discourage mixin abuse, the Validation module includes
29
+ # all of its submodules into its eigenclass at load-time. This means that every
30
+ # validation method "is_foo" provided by _any_ submodule can be accessed simply
31
+ # with a call to Validation.is_foo.
32
+ #
33
+ module Validation
34
+
35
+ end
36
+ end
37
+
38
+ Dir[File.expand_path('../validation/*.rb', __FILE__)].each do |filename|
39
+ require filename
40
+ end
41
+
42
+ RightSupport::Validation.constants.each do |const|
43
+ const = RightSupport::Validation.const_get(const) #string to constant
44
+ RightSupport::Validation.extend(const)
45
+ end
@@ -0,0 +1,9 @@
1
+ require 'right_support/kernel_extensions'
2
+ require 'right_support/filter_logger'
3
+ require 'right_support/system_logger'
4
+ require 'right_support/tag_logger'
5
+ require 'right_support/rack/custom_logger'
6
+
7
+ require 'right_support/validation'
8
+
9
+ require 'right_support/net'
@@ -0,0 +1,31 @@
1
+ # -*- mode: ruby; encoding: utf-8 -*-
2
+
3
+ require 'rubygems'
4
+
5
+ spec = Gem::Specification.new do |s|
6
+ s.required_rubygems_version = nil if s.respond_to? :required_rubygems_version=
7
+ s.required_ruby_version = Gem::Requirement.new(">= 1.8.7")
8
+
9
+ s.name = 'right_support'
10
+ s.version = '0.8.0'
11
+ s.date = '2011-04-09'
12
+
13
+ s.authors = ['Tony Spataro']
14
+ s.email = 'tony@rightscale.com'
15
+ s.homepage= 'https://github.com/xeger/right_support'
16
+
17
+ s.summary = %q{Reusable foundation code.}
18
+ s.description = %q{A toolkit of useful foundation code: logging, input validation, etc.}
19
+
20
+ s.add_development_dependency('rake', [">= 0.8.7"])
21
+ s.add_development_dependency('ruby-debug', [">= 0.10"])
22
+ s.add_development_dependency('rspec', ["~> 1.3"])
23
+ s.add_development_dependency('cucumber', ["~> 0.8"])
24
+ s.add_development_dependency('flexmock', ["~> 0.8"])
25
+ s.add_development_dependency('net-ssh', ["~> 2.0"])
26
+ s.add_development_dependency('rest-client', ["~> 1.6"])
27
+
28
+ basedir = File.dirname(__FILE__)
29
+ candidates = ['right_support.gemspec', 'LICENSE', 'README.rdoc'] + Dir['lib/**/*']
30
+ s.files = candidates.sort
31
+ end
metadata ADDED
@@ -0,0 +1,188 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: right_support
3
+ version: !ruby/object:Gem::Version
4
+ hash: 63
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 8
9
+ - 0
10
+ version: 0.8.0
11
+ platform: ruby
12
+ authors:
13
+ - Tony Spataro
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-04-09 00:00:00 -07:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rake
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 49
30
+ segments:
31
+ - 0
32
+ - 8
33
+ - 7
34
+ version: 0.8.7
35
+ type: :development
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: ruby-debug
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 31
46
+ segments:
47
+ - 0
48
+ - 10
49
+ version: "0.10"
50
+ type: :development
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ name: rspec
54
+ prerelease: false
55
+ requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ~>
59
+ - !ruby/object:Gem::Version
60
+ hash: 9
61
+ segments:
62
+ - 1
63
+ - 3
64
+ version: "1.3"
65
+ type: :development
66
+ version_requirements: *id003
67
+ - !ruby/object:Gem::Dependency
68
+ name: cucumber
69
+ prerelease: false
70
+ requirement: &id004 !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ hash: 27
76
+ segments:
77
+ - 0
78
+ - 8
79
+ version: "0.8"
80
+ type: :development
81
+ version_requirements: *id004
82
+ - !ruby/object:Gem::Dependency
83
+ name: flexmock
84
+ prerelease: false
85
+ requirement: &id005 !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ~>
89
+ - !ruby/object:Gem::Version
90
+ hash: 27
91
+ segments:
92
+ - 0
93
+ - 8
94
+ version: "0.8"
95
+ type: :development
96
+ version_requirements: *id005
97
+ - !ruby/object:Gem::Dependency
98
+ name: net-ssh
99
+ prerelease: false
100
+ requirement: &id006 !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ~>
104
+ - !ruby/object:Gem::Version
105
+ hash: 3
106
+ segments:
107
+ - 2
108
+ - 0
109
+ version: "2.0"
110
+ type: :development
111
+ version_requirements: *id006
112
+ - !ruby/object:Gem::Dependency
113
+ name: rest-client
114
+ prerelease: false
115
+ requirement: &id007 !ruby/object:Gem::Requirement
116
+ none: false
117
+ requirements:
118
+ - - ~>
119
+ - !ruby/object:Gem::Version
120
+ hash: 3
121
+ segments:
122
+ - 1
123
+ - 6
124
+ version: "1.6"
125
+ type: :development
126
+ version_requirements: *id007
127
+ description: "A toolkit of useful foundation code: logging, input validation, etc."
128
+ email: tony@rightscale.com
129
+ executables: []
130
+
131
+ extensions: []
132
+
133
+ extra_rdoc_files: []
134
+
135
+ files:
136
+ - LICENSE
137
+ - README.rdoc
138
+ - lib/right_support.rb
139
+ - lib/right_support/filter_logger.rb
140
+ - lib/right_support/kernel_extensions.rb
141
+ - lib/right_support/net.rb
142
+ - lib/right_support/net/request_balancer.rb
143
+ - lib/right_support/net/rest.rb
144
+ - lib/right_support/rack/custom_logger.rb
145
+ - lib/right_support/system_logger.rb
146
+ - lib/right_support/tag_logger.rb
147
+ - lib/right_support/validation.rb
148
+ - lib/right_support/validation/openssl.rb
149
+ - lib/right_support/validation/ssh.rb
150
+ - right_support.gemspec
151
+ has_rdoc: true
152
+ homepage: https://github.com/xeger/right_support
153
+ licenses: []
154
+
155
+ post_install_message:
156
+ rdoc_options: []
157
+
158
+ require_paths:
159
+ - lib
160
+ required_ruby_version: !ruby/object:Gem::Requirement
161
+ none: false
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ hash: 57
166
+ segments:
167
+ - 1
168
+ - 8
169
+ - 7
170
+ version: 1.8.7
171
+ required_rubygems_version: !ruby/object:Gem::Requirement
172
+ none: false
173
+ requirements:
174
+ - - ">="
175
+ - !ruby/object:Gem::Version
176
+ hash: 3
177
+ segments:
178
+ - 0
179
+ version: "0"
180
+ requirements: []
181
+
182
+ rubyforge_project:
183
+ rubygems_version: 1.3.7
184
+ signing_key:
185
+ specification_version: 3
186
+ summary: Reusable foundation code.
187
+ test_files: []
188
+