right_support 2.4.1 → 2.5.0
Sign up to get free protection for your applications and to get access to all the features.
@@ -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
|