right_support 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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
+