right_support 2.4.1 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,223 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
module RightSupport::Data
|
5
|
+
# Utility class that implements true, lossless Ruby-to-JSON serialization.
|
6
|
+
# With a few small exceptions, this module can #dump any Ruby object in your
|
7
|
+
# VM, and later #load the exact same object from its serialized representation.
|
8
|
+
# It can also be used with encoding schemes other than JSON, e.g. msgpack.
|
9
|
+
#
|
10
|
+
# This class works by transforming Ruby object graphs into an intermediate
|
11
|
+
# representation that consists solely of JSON-clean Ruby objects (String,
|
12
|
+
# Integer, ...). This intermediate format is dubbed "JSONish", and it can
|
13
|
+
# be transformed to JSON and back without losing data or creating ambiguity.
|
14
|
+
#
|
15
|
+
# If you use the class-level (::load and ::dump) interface to this class,
|
16
|
+
# it always uses JSON serialization by default. If you construct an instance,
|
17
|
+
# you can control which encoding scheme to use, as well as which Hash key to use
|
18
|
+
# to mark a serialized object.
|
19
|
+
#
|
20
|
+
# === JSONish Object Representation
|
21
|
+
# Most Ruby simple types (String, Integer, Float, true/false/nil) are
|
22
|
+
# represented by their corresponding JSON type. However, some JSONish
|
23
|
+
# values have special meaning:
|
24
|
+
# * Strings beginning with a ':' represent a Ruby Symbol
|
25
|
+
# * Strings in the ISO 8601 timestamp format represent a Ruby Time
|
26
|
+
#
|
27
|
+
# To avoid ambiguity due to Ruby Strings that happen to look like a
|
28
|
+
# JSONish Time or Symbol, some Strings are "object-escaped," meaning they
|
29
|
+
# are represented as a JSON object with _ruby_class:String.
|
30
|
+
#
|
31
|
+
# Arbitrary Ruby objects are represented as a Hash that contains a special
|
32
|
+
# key '_ruby_class'. The other key/value pairs of the hash consist of the
|
33
|
+
# object's instance variables. Any object whose state is solely contained
|
34
|
+
# in its instance variables is therefore eligible for serialization.
|
35
|
+
#
|
36
|
+
# JSONish also has special-purpose logic for some Ruby built-ins,
|
37
|
+
# allowing them to be serialized even though their state is not contained in
|
38
|
+
# instance variables. The following are all serializable built-ins:
|
39
|
+
# * Class
|
40
|
+
# * Module
|
41
|
+
# * String (see below).
|
42
|
+
#
|
43
|
+
# === Capabilities
|
44
|
+
# The serializer can handle any Ruby object that uses instance variables to
|
45
|
+
# record state. It cleanly round-trips the following Ruby object types:
|
46
|
+
# * Collections (Hash, Array)
|
47
|
+
# * JSON-clean data (Numeric, String, true, false, nil)
|
48
|
+
# * Symbol
|
49
|
+
# * Time
|
50
|
+
# * Class
|
51
|
+
# * Module
|
52
|
+
#
|
53
|
+
# === Known Limitations
|
54
|
+
# Cannot record the following kinds of state:
|
55
|
+
# * Class variables of objects
|
56
|
+
# * State that lives outside of instance variables, e.g. IO#fileno
|
57
|
+
#
|
58
|
+
# Cannot cleanly round-trip the following:
|
59
|
+
# * Objects that represent callable code: Proc, Lambda, etc.
|
60
|
+
# * High-precision floating point numbers (due to truncation)
|
61
|
+
# * Times with timezone other than UTC or precision greater than 1 sec
|
62
|
+
# * Hash keys that are anything other than a String (depends on JSON parser)
|
63
|
+
# * Hashes that contain a String key whose value is '_ruby_class'
|
64
|
+
class Serializer
|
65
|
+
if require_succeeds?('yajl')
|
66
|
+
Encoder = ::Yajl
|
67
|
+
elsif require_succeeds?('oj')
|
68
|
+
Encoder = ::Oj
|
69
|
+
elsif require_succeeds?('json')
|
70
|
+
Encoder = ::JSON
|
71
|
+
else
|
72
|
+
Encoder = nil
|
73
|
+
end unless defined?(Encoder)
|
74
|
+
|
75
|
+
# Format string to use when sprintf'ing a JSONish Time
|
76
|
+
TIME_FORMAT = '%4.4d-%2.2d-%2.2dT%2.2d:%2.2d:%2.2dZ'
|
77
|
+
# Pattern to match Strings against for object-escaping
|
78
|
+
TIME_PATTERN = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z$/
|
79
|
+
# Special key used as a pseudo-instance-variable for Class and Module
|
80
|
+
CLASS_ESCAPE_KEY = 'name'
|
81
|
+
# Special key used as a pseudo-instance-variable for String
|
82
|
+
STRING_ESCAPE_KEY = 'value'
|
83
|
+
|
84
|
+
DEFAULT_OPTIONS = {
|
85
|
+
:marker => '_ruby_class',
|
86
|
+
:encoder => Encoder
|
87
|
+
}
|
88
|
+
|
89
|
+
def self.load(data)
|
90
|
+
self.new.load(data)
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.dump(object)
|
94
|
+
self.new.dump(object)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Instantiate a Serializer instance.
|
98
|
+
# @option options :encoder [#load, #dump] underlying data-encoder to use. Defaults to any available JSON gem.
|
99
|
+
# @option options :marker special object key to use when representing a serialized Ruby object. Defaults to '_ruby_class'.
|
100
|
+
def initialize(options={})
|
101
|
+
options = DEFAULT_OPTIONS.merge(options)
|
102
|
+
@marker = options[:marker]
|
103
|
+
@encoder = options[:encoder]
|
104
|
+
end
|
105
|
+
|
106
|
+
# @param [String] data valid JSON document representing a serialized object
|
107
|
+
# @return [Object] unserialized Ruby object
|
108
|
+
def load(data)
|
109
|
+
jsonish = @encoder.load(data)
|
110
|
+
jsonish_to_object(jsonish)
|
111
|
+
end
|
112
|
+
|
113
|
+
# @param [Object] object any Ruby object
|
114
|
+
# @return [String] JSON document representing the serialized object
|
115
|
+
def dump(object)
|
116
|
+
jsonish = object_to_jsonish(object)
|
117
|
+
@encoder.dump(jsonish)
|
118
|
+
end
|
119
|
+
|
120
|
+
protected
|
121
|
+
|
122
|
+
# Given an Object, transform it into a JSONish Ruby structure.
|
123
|
+
# @param [Object] object any Ruby object
|
124
|
+
# @return [Object] JSONish representation of input object
|
125
|
+
def object_to_jsonish(object)
|
126
|
+
case object
|
127
|
+
when String
|
128
|
+
if (object =~ /^:/ ) || (object =~ TIME_PATTERN)
|
129
|
+
# Strings that look like a Symbol or Time must be object-escaped.
|
130
|
+
{@marker => String.name,
|
131
|
+
STRING_ESCAPE_KEY => object}
|
132
|
+
else
|
133
|
+
object
|
134
|
+
end
|
135
|
+
when Fixnum, Float, TrueClass, FalseClass, NilClass
|
136
|
+
object
|
137
|
+
when Hash
|
138
|
+
object.inject({}) do |result, (k, v)|
|
139
|
+
result[object_to_jsonish(k)] = object_to_jsonish(v)
|
140
|
+
result
|
141
|
+
end
|
142
|
+
when Array
|
143
|
+
object.map { |e| object_to_jsonish(e) }
|
144
|
+
when Symbol
|
145
|
+
# Ruby Symbol is represented as a string beginning with ':'
|
146
|
+
":#{object}"
|
147
|
+
when Time
|
148
|
+
# Ruby Time is represented as an ISO 8601 timestamp in UTC timeztone
|
149
|
+
utc = object.utc
|
150
|
+
TIME_FORMAT % [utc.year, utc.mon, utc.day, utc.hour, utc.min, utc.sec]
|
151
|
+
when Class, Module
|
152
|
+
# Ruby Class/Module needs special handling - no instance vars
|
153
|
+
{ @marker => object.class.name,
|
154
|
+
CLASS_ESCAPE_KEY => object.name }
|
155
|
+
else
|
156
|
+
# Generic serialized object; convert to Hash.
|
157
|
+
hash = {}
|
158
|
+
hash[@marker] = object.class.name
|
159
|
+
|
160
|
+
object.instance_variables.each do |var|
|
161
|
+
hash[ var[1..-1] ] = object_to_jsonish(object.instance_variable_get(var))
|
162
|
+
end
|
163
|
+
|
164
|
+
hash
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Given a JSONish structure, transform it back to a Ruby object.
|
169
|
+
# @param [Object] jsonish JSONish Ruby structure
|
170
|
+
# @return [Object] unserialized Ruby object
|
171
|
+
def jsonish_to_object(jsonish)
|
172
|
+
case jsonish
|
173
|
+
when String
|
174
|
+
if jsonish =~ /^:/
|
175
|
+
# JSONish Symbol
|
176
|
+
jsonish[1..-1].to_sym
|
177
|
+
elsif TIME_PATTERN.match(jsonish)
|
178
|
+
# JSONish Time
|
179
|
+
Time.parse(jsonish)
|
180
|
+
else
|
181
|
+
# Normal String
|
182
|
+
jsonish
|
183
|
+
end
|
184
|
+
when Fixnum, Float, TrueClass, FalseClass, NilClass
|
185
|
+
jsonish
|
186
|
+
when Hash
|
187
|
+
if jsonish.key?(@marker) # We have a serialized Ruby object!
|
188
|
+
hash = jsonish
|
189
|
+
klass = hash.delete(@marker)
|
190
|
+
case klass
|
191
|
+
when Class.name, Module.name
|
192
|
+
# Serialized instance of Ruby Class or Module
|
193
|
+
jsonish = hash.delete(CLASS_ESCAPE_KEY).to_const
|
194
|
+
when String.name
|
195
|
+
# Object-escaped Ruby String
|
196
|
+
jsonish = String.new(hash.delete(STRING_ESCAPE_KEY))
|
197
|
+
when Time.name
|
198
|
+
# Object-escaped Ruby String
|
199
|
+
jsonish = Time.at(hash.delete(TIME_ESCAPE_KEY))
|
200
|
+
else
|
201
|
+
# Generic serialized object
|
202
|
+
klass = klass.to_const
|
203
|
+
jsonish = klass.allocate
|
204
|
+
hash.each_pair do |k, v|
|
205
|
+
jsonish.instance_variable_set("@#{k}", jsonish_to_object(v))
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
jsonish
|
210
|
+
else # We have a plain old Ruby Hash
|
211
|
+
jsonish.inject({}) do |result, (k, v)|
|
212
|
+
result[jsonish_to_object(k)] = jsonish_to_object(v)
|
213
|
+
result
|
214
|
+
end
|
215
|
+
end
|
216
|
+
when Array
|
217
|
+
jsonish.map { |e| jsonish_to_object(e) }
|
218
|
+
else
|
219
|
+
raise ArgumentError, "Non-JSONish object of type '#{jsonish.class}'"
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
data/lib/right_support/data.rb
CHANGED
@@ -0,0 +1,144 @@
|
|
1
|
+
# Copyright (c) 2009-2011 RightScale, Inc, All Rights Reserved Worldwide.
|
2
|
+
#
|
3
|
+
# THIS PROGRAM IS CONFIDENTIAL AND PROPRIETARY TO RIGHTSCALE
|
4
|
+
# AND CONSTITUTES A VALUABLE TRADE SECRET. Any unauthorized use,
|
5
|
+
# reproduction, modification, or disclosure of this program is
|
6
|
+
# strictly prohibited. Any use of this program by an authorized
|
7
|
+
# licensee is strictly subject to the terms and conditions,
|
8
|
+
# including confidentiality obligations, set forth in the applicable
|
9
|
+
# License Agreement between RightScale.com, Inc. and the licensee.
|
10
|
+
|
11
|
+
module RightSupport::Net
|
12
|
+
# Class provides S3 functionality.
|
13
|
+
# As part of RightSupport S3Helper does not include Rightscale::S3 and Encryptor modules.
|
14
|
+
# This modules must be included in application.
|
15
|
+
#
|
16
|
+
# Example:
|
17
|
+
#
|
18
|
+
# require 'right_support'
|
19
|
+
# require 'right_aws'
|
20
|
+
# require 'encryptor'
|
21
|
+
#
|
22
|
+
# s3_config = YAML.load_file(File.expand_path("../../config/s3.yml",__FILE__))[ENV['RACK_ENV']]
|
23
|
+
# RightSupport::Net::S3Helper.init(s3_config, Rightscale::S3, Encryptor)
|
24
|
+
# s3_health = RightSupport::Net::S3Helper.health_check
|
25
|
+
class S3Helper
|
26
|
+
# Init() is the first method which must be called.
|
27
|
+
# This is configuration and integration with S3 and Encryptor
|
28
|
+
#
|
29
|
+
# @param [Hash] config config for current environment (from YAML config file)
|
30
|
+
# creds:
|
31
|
+
# aws_access_key_id: String
|
32
|
+
# aws_secret_access_key: String
|
33
|
+
# bucket_name: String
|
34
|
+
# master_secret: String
|
35
|
+
# @param [Class] s3 Rightscale::S3 class is used to connect to S3
|
36
|
+
# @param [Class] encryptor Class which will be used to encrypt data
|
37
|
+
# encrypt() and decrypt() methods must be implemented
|
38
|
+
#
|
39
|
+
# Init() does not return anything
|
40
|
+
def self.init(config, s3, encryptor)
|
41
|
+
@config = config
|
42
|
+
@s3class = s3
|
43
|
+
@encryptor = encryptor
|
44
|
+
end
|
45
|
+
# Checks S3 credentials syntax in config parameter
|
46
|
+
#
|
47
|
+
# @return [Boolean] true if s3 creadentials are ok or false if not
|
48
|
+
def self.s3_enabled?
|
49
|
+
return @s3_enabled if @s3_enabled
|
50
|
+
@s3_enabled ||= !config.empty? &&
|
51
|
+
!config["creds"].empty? &&
|
52
|
+
!config["creds"]["aws_access_key_id"].nil? &&
|
53
|
+
config["creds"]["aws_access_key_id"] != "@@AWS_ACCESS_KEY_ID@@"
|
54
|
+
end
|
55
|
+
# Returns @config parameter
|
56
|
+
def self.config
|
57
|
+
@config
|
58
|
+
end
|
59
|
+
# Creates (if does not exist) and return S3 object
|
60
|
+
def self.s3
|
61
|
+
@s3 ||= @s3class.new config["creds"]["aws_access_key_id"], config["creds"]["aws_secret_access_key"]
|
62
|
+
end
|
63
|
+
# Creates S3 Bucket object using created in s3() object
|
64
|
+
def self.bucket
|
65
|
+
#TO DISCUSS: second param 'true' means 'create new bucket'
|
66
|
+
#@bucket ||= s3.bucket(config["bucket_name"], true)
|
67
|
+
@bucket ||= s3.bucket(config["bucket_name"])
|
68
|
+
end
|
69
|
+
# Returns Master secret from config
|
70
|
+
def self.master_secret
|
71
|
+
config["master_secret"]
|
72
|
+
end
|
73
|
+
# Downloads data from S3 Bucket
|
74
|
+
#
|
75
|
+
# @param [String] key Name of the bucket key
|
76
|
+
# @param [Block] blck Ruby code wich will be done by Bucket object
|
77
|
+
#
|
78
|
+
# @return [String] S3 bucket's key content in plain/text format
|
79
|
+
def self.get(key, &blck)
|
80
|
+
return nil unless s3_enabled?
|
81
|
+
|
82
|
+
object = bucket.key(key, true, &blck)
|
83
|
+
return nil if object.nil?
|
84
|
+
|
85
|
+
# don't decrypt/verify unencrypted values
|
86
|
+
return object.data if object.meta_headers["digest"].nil?
|
87
|
+
|
88
|
+
ciphertext = object.data
|
89
|
+
passphrase = "#{key}:#{master_secret}"
|
90
|
+
plaintext = @encryptor.decrypt(:key=>passphrase, :value=>ciphertext)
|
91
|
+
digest = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, passphrase, plaintext)
|
92
|
+
|
93
|
+
if digest == object.meta_headers["digest"]
|
94
|
+
return plaintext
|
95
|
+
else
|
96
|
+
raise "digest for key:#{key} in s3 does not match calculated digest."
|
97
|
+
end
|
98
|
+
end
|
99
|
+
# Upload data to S3 Bucket
|
100
|
+
#
|
101
|
+
# @param [String] key Name of the bucket key
|
102
|
+
# @param [String] plaintext Data which should be saved in the Bucket
|
103
|
+
# @param [Block] blck Ruby code wich will be done by Bucket object
|
104
|
+
#
|
105
|
+
def self.post(key, plaintext, &blck)
|
106
|
+
passphrase = "#{key}:#{master_secret}"
|
107
|
+
plaintext = "-- no detail --" unless plaintext && plaintext.size > 0
|
108
|
+
ciphertext = @encryptor.encrypt(:key=>passphrase, :value=>plaintext)
|
109
|
+
digest = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, passphrase, plaintext)
|
110
|
+
bucket.put(key, ciphertext, {"digest" => digest}, &blck)
|
111
|
+
end
|
112
|
+
# Checks if it is possible to connect to the S3 Bucket.
|
113
|
+
#
|
114
|
+
# @return [String or True] If test was successful then return true
|
115
|
+
# Else return error message
|
116
|
+
def self.health_check
|
117
|
+
config
|
118
|
+
s3
|
119
|
+
# test config file
|
120
|
+
return 'S3 Config file: Credentials: Syntax error.' unless s3_enabled?
|
121
|
+
["bucket_name", "master_secret"].each do |conf|
|
122
|
+
return "S3 Config file: #{conf.upcase}: Syntax error." if config[conf].nil? || config[conf] == '' || config[conf] == "@@#{conf.upcase}@@"
|
123
|
+
end
|
124
|
+
# test connection
|
125
|
+
original_text = 'heath check'
|
126
|
+
test_key = 'ping'
|
127
|
+
begin
|
128
|
+
post(test_key, original_text)
|
129
|
+
rescue Exception => e
|
130
|
+
return e.message
|
131
|
+
end
|
132
|
+
begin
|
133
|
+
result_text = get(test_key)
|
134
|
+
rescue Exception => e
|
135
|
+
return e.message
|
136
|
+
end
|
137
|
+
return 'Sended text and returned one are not equal. Possible master_secret problem' if result_text != original_text
|
138
|
+
# retrurn true if there were not errors
|
139
|
+
true
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
data/lib/right_support/net.rb
CHANGED
data/right_support.gemspec
CHANGED
@@ -7,8 +7,8 @@ spec = Gem::Specification.new do |s|
|
|
7
7
|
s.required_ruby_version = Gem::Requirement.new(">= 1.8.7")
|
8
8
|
|
9
9
|
s.name = 'right_support'
|
10
|
-
s.version = '2.
|
11
|
-
s.date = '2012-09-
|
10
|
+
s.version = '2.5.0'
|
11
|
+
s.date = '2012-09-19'
|
12
12
|
|
13
13
|
s.authors = ['Tony Spataro', 'Sergey Sergyenko', 'Ryan Williamson', 'Lee Kirchhoff', 'Sergey Enin']
|
14
14
|
s.email = 'support@rightscale.com'
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: right_support
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 27
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 2
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 2.
|
8
|
+
- 5
|
9
|
+
- 0
|
10
|
+
version: 2.5.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Tony Spataro
|
@@ -19,7 +19,7 @@ autorequire:
|
|
19
19
|
bindir: bin
|
20
20
|
cert_chain: []
|
21
21
|
|
22
|
-
date: 2012-09-
|
22
|
+
date: 2012-09-19 00:00:00 -07:00
|
23
23
|
default_executable:
|
24
24
|
dependencies: []
|
25
25
|
|
@@ -43,6 +43,7 @@ files:
|
|
43
43
|
- lib/right_support/crypto/signed_hash.rb
|
44
44
|
- lib/right_support/data.rb
|
45
45
|
- lib/right_support/data/hash_tools.rb
|
46
|
+
- lib/right_support/data/serializer.rb
|
46
47
|
- lib/right_support/data/uuid.rb
|
47
48
|
- lib/right_support/db.rb
|
48
49
|
- lib/right_support/db/cassandra_model.rb
|
@@ -62,6 +63,7 @@ files:
|
|
62
63
|
- lib/right_support/net/lb/round_robin.rb
|
63
64
|
- lib/right_support/net/lb/sticky.rb
|
64
65
|
- lib/right_support/net/request_balancer.rb
|
66
|
+
- lib/right_support/net/s3_helper.rb
|
65
67
|
- lib/right_support/net/ssl.rb
|
66
68
|
- lib/right_support/net/ssl/open_ssl_patch.rb
|
67
69
|
- lib/right_support/net/string_encoder.rb
|