fog_tracker 0.2.2 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +36 -20
- data/config/accounts.yml.example +18 -14
- data/lib/fog_tracker/account_tracker.rb +8 -3
- data/lib/fog_tracker/collection_tracker.rb +5 -3
- data/lib/fog_tracker/extensions/fog_model.rb +2 -2
- data/lib/fog_tracker/query/query_processor.rb +4 -4
- data/lib/fog_tracker/tracker.rb +7 -0
- data/lib/fog_tracker/version.rb +1 -1
- data/spec/lib/fog_tracker/account_tracker_spec.rb +8 -2
- data/spec/lib/fog_tracker/collection_tracker_spec.rb +7 -0
- data/spec/lib/fog_tracker/tracker_spec.rb +11 -0
- data/spec/support/_mocks.rb +1 -1
- metadata +18 -18
data/README.md
CHANGED
@@ -35,23 +35,27 @@ How is it [done]? (Usage)
|
|
35
35
|
|
36
36
|
Here are the contents of a sample `accounts.yml`:
|
37
37
|
|
38
|
-
AWS EC2
|
39
|
-
:provider: AWS
|
40
|
-
:service: Compute
|
38
|
+
AWS EC2 production account: # The account name - can be anything
|
39
|
+
:provider: AWS # This is the Fog provider Module
|
40
|
+
:service: Compute # The Fog service Module. So, Fog::Compute::AWS
|
41
41
|
:credentials:
|
42
42
|
:aws_access_key_id: XXXXXXXXXXXXXXXXXXXX
|
43
43
|
:aws_secret_access_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
44
|
-
:
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
:
|
44
|
+
:delay: 120 # Wait time between successive pollings (in seconds)
|
45
|
+
:exclude_resources:
|
46
|
+
- :account # No need to poll for accounts - those are listed here
|
47
|
+
- :flavors # You may or may not want EC2 server types
|
48
|
+
- :images # Takes a while to list all AMIs (works though)
|
49
|
+
AWS S3 development account:
|
50
|
+
:provider: AWS
|
51
|
+
:service: Storage
|
52
|
+
:exclude_resources:
|
53
|
+
#- :directories # The S3 buckets - fetch the list
|
54
|
+
- :files # "secondary" entity to buckets - does not work yet
|
51
55
|
:credentials:
|
52
|
-
:
|
53
|
-
:
|
54
|
-
:
|
56
|
+
:aws_access_key_id: XXXXXXXXXXXXXXXXXXXX
|
57
|
+
:aws_secret_access_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
58
|
+
:delay: 150
|
55
59
|
|
56
60
|
2) Call `start` on the Tracker. It will run asynchronously, with one thread per account. At any time, you can call `start` or `stop` on it, and query the resulting collections of Fog Resource objects.
|
57
61
|
|
@@ -63,7 +67,7 @@ How is it [done]? (Usage)
|
|
63
67
|
tracker.query("*::Compute::*::servers")
|
64
68
|
|
65
69
|
# get all Amazon EC2 Resources, of all types, across all accounts
|
66
|
-
tracker["*::Compute::AWS::*"] # the [] operator is
|
70
|
+
tracker["*::Compute::AWS::*"] # the [] operator is aliased to query()
|
67
71
|
|
68
72
|
# get all S3 buckets in a given account
|
69
73
|
tracker["my production account::Storage::AWS::directories"]
|
@@ -75,7 +79,7 @@ How is it [done]? (Usage)
|
|
75
79
|
|
76
80
|
tracker.query("*::*::*::*"){|r| puts "Found #{r.class} #{r.identity}"}
|
77
81
|
|
78
|
-
* You can pass a callback Proc to the Tracker at initialization, which will be invoked whenever all an account's Resources have been updated. It should accept an Array
|
82
|
+
* You can pass a callback Proc to the Tracker at initialization, which will be invoked whenever all an account's Resources have been updated. It should accept an Array as its first parameter, which will contain the all the account's Resources. Like this:
|
79
83
|
|
80
84
|
FogTracker::Tracker.new(YAML::load(File.read 'accounts.yml'),
|
81
85
|
:callback => Proc.new do |resources|
|
@@ -84,15 +88,27 @@ How is it [done]? (Usage)
|
|
84
88
|
end
|
85
89
|
).start
|
86
90
|
|
87
|
-
* The resources returned from a query are all Fog::Model objects, but they are "decorated" with some extra methods for fetching the account information, or for fetching more resources. This simplifies the code that consumes the query results, because it does not have to know anything about the tracker. Here are the methods added by {FogTracker::Extensions::FogModel}:
|
91
|
+
* The resources returned from a query are all Fog::Model objects, but they are "decorated" with some extra methods for fetching the account information, or for fetching more resources. This simplifies the code that consumes the query results, because it does not have to know anything about the tracker. Here are the some of the methods added by {FogTracker::Extensions::FogModel}:
|
88
92
|
1. `tracker_account` returns a Hash of the Resource's account information _(:name is added; :credentials are removed)_.
|
89
|
-
2. `
|
90
|
-
3. `
|
91
|
-
4. `
|
93
|
+
2. `tracker_description` returns a descriptive identifier (a String) that is unique to this resource.
|
94
|
+
3. `tracker_query(query_string)` queries the tracker for more resources.
|
95
|
+
4. `account_resources(collection_query)` also returns an Array of resources, but only from the same account. (This is essentially shorthand for `tracker_query("account::service::provider::#{collection_query}")`)
|
92
96
|
|
93
97
|
* Any Exceptions that occur in the Tracker's polling threads are rescued and logged. If you want to take further action, you can initialize the Tracker with an `:error_callback` Proc. This is similar to the Account update `:callback` above, except that the parameter for `:error_callback` should be an Exception instead of an Array of Resources.
|
94
98
|
|
95
|
-
* The Tracker can also be used synchronously. Its `update` method polls all accounts immediately, and waits for the result (the updated Array of resource objects) in the current thread.
|
99
|
+
* The Tracker can also be used synchronously. Its `update` method polls all accounts immediately, one at a time, and waits for the result (the updated Array of resource objects) in the current thread. In this mode, any exceptions are raised immediately.
|
100
|
+
|
101
|
+
----------------
|
102
|
+
*Known Limitations / Bugs*
|
103
|
+
|
104
|
+
* Some Fog resources are not currently supported, because polling them depends on making multiple calls to the service provider, once for each primary key of some other resource. These "secondary resources" include:
|
105
|
+
* S3 Objects (Fog::Storage[:aws].files) - each is dependent
|
106
|
+
on its bucket (Fog::Storage[:aws].directories)
|
107
|
+
* Route 53 addresses (Fog::DNS[:aws].addresses) - each is dependent
|
108
|
+
on its DNS zone (Fog::DNS[:aws].zones)
|
109
|
+
|
110
|
+
Supporting these would involve hard-coding these dependencies into this gem. Currently, all Fog resource information is introspected, which allows this gem to track new Fog functionality with no update. If you really want to get information on these resources, you can traverse the Fog resource graph in your Account update `:callback`.
|
111
|
+
|
96
112
|
|
97
113
|
----------------
|
98
114
|
Who is it? (Contribution)
|
data/config/accounts.yml.example
CHANGED
@@ -1,25 +1,29 @@
|
|
1
|
-
AWS EC2 production account:
|
2
|
-
:provider: AWS
|
3
|
-
:service: Compute
|
4
|
-
:credentials:
|
1
|
+
AWS EC2 production account: # The account name - can be anything
|
2
|
+
:provider: AWS # This is the Fog provider Module
|
3
|
+
:service: Compute # The Fog service Module. So, Fog::Compute::AWS
|
4
|
+
:credentials:
|
5
5
|
:aws_access_key_id: XXXXXXXXXXXXXXXXXXXX
|
6
6
|
:aws_secret_access_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
7
|
-
:
|
7
|
+
:delay: 120 # Wait time between successive pollings (in seconds)
|
8
8
|
:exclude_resources:
|
9
|
-
|
9
|
+
- :account # No need to poll for accounts - those are listed here
|
10
|
+
- :flavors # You may or may not want EC2 server types
|
11
|
+
- :images # Takes a while to list all AMIs (works though)
|
12
|
+
AWS S3 development account:
|
10
13
|
:provider: AWS
|
11
|
-
:service:
|
12
|
-
:credentials:
|
14
|
+
:service: Storage
|
15
|
+
:credentials:
|
13
16
|
:aws_access_key_id: XXXXXXXXXXXXXXXXXXXX
|
14
17
|
:aws_secret_access_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
15
|
-
:
|
18
|
+
:delay: 150
|
16
19
|
:exclude_resources:
|
17
|
-
-
|
18
|
-
- :
|
19
|
-
Rackspace development account:
|
20
|
+
#- :directories # The S3 buckets - fetch the list
|
21
|
+
- :files # "secondary" entity to buckets - does not work yet
|
22
|
+
Rackspace development account:
|
20
23
|
:provider: Rackspace
|
21
24
|
:service: Compute
|
22
|
-
:credentials:
|
25
|
+
:credentials:
|
23
26
|
:rackspace_api_key: XXXXXXXXXXXXXXXXXXXX
|
24
27
|
:rackspace_username: XXXXXXXXX
|
25
|
-
:
|
28
|
+
:delay: 180
|
29
|
+
:exclude_resources: # Don't know which of these work yet...
|
@@ -12,6 +12,8 @@ module FogTracker
|
|
12
12
|
attr_reader :log
|
13
13
|
# How long to wait between successive polling of this account (Integer)
|
14
14
|
attr_reader :delay
|
15
|
+
# The time that the *second-to-last* successful poll finished
|
16
|
+
attr_reader :preceeding_update_time
|
15
17
|
|
16
18
|
# Creates an object for tracking all collections in a single Fog account
|
17
19
|
# @param [String] account_name a human-readable name for the account
|
@@ -28,9 +30,9 @@ module FogTracker
|
|
28
30
|
@account = account
|
29
31
|
@callback = options[:callback]
|
30
32
|
@log = options[:logger] || FogTracker.default_logger
|
31
|
-
@delay = options[:delay] || account[:
|
33
|
+
@delay = options[:delay] || account[:delay] ||
|
32
34
|
FogTracker::DEFAULT_POLLING_TIME
|
33
|
-
@account[:
|
35
|
+
@account[:delay] = @delay
|
34
36
|
@error_proc = options[:error_callback]
|
35
37
|
@log.debug "Creating tracker for account #{@name}."
|
36
38
|
create_collection_trackers
|
@@ -58,7 +60,7 @@ module FogTracker
|
|
58
60
|
# Stops polling this tracker's account
|
59
61
|
def stop
|
60
62
|
if running?
|
61
|
-
@log.info "Stopping tracker for #{name}..."
|
63
|
+
@log.info "Stopping tracker for #{@name}..."
|
62
64
|
@timer.kill
|
63
65
|
@timer = nil
|
64
66
|
else
|
@@ -71,6 +73,9 @@ module FogTracker
|
|
71
73
|
begin
|
72
74
|
@log.info "Polling account #{@name}..."
|
73
75
|
@collection_trackers.each {|tracker| tracker.update}
|
76
|
+
@preceeding_update_time = @most_recent_update
|
77
|
+
@most_recent_update = Time.now
|
78
|
+
@log.info "Polled account #{@name}"
|
74
79
|
@callback.call(all_resources) if @callback
|
75
80
|
rescue Exception => e
|
76
81
|
@log.error "Exception polling account #{name}: #{e.message}"
|
@@ -24,26 +24,28 @@ module FogTracker
|
|
24
24
|
# Polls the {AccountTracker}'s connection for updated info on all existing
|
25
25
|
# instances of this tracker's resource_type
|
26
26
|
def update
|
27
|
-
|
27
|
+
new_collection = Array.new
|
28
28
|
fog_collection = @account_tracker.connection.send(@type) || Array.new
|
29
29
|
@log.info "Fetching #{fog_collection.count} #{@type} on #{@account_name}."
|
30
|
-
new_collection = Array.new
|
31
30
|
# Here's where most of the network overhead is actually incurred
|
32
31
|
fog_collection.each do |resource|
|
33
32
|
@log.debug "Fetching resource: #{resource.class} #{resource.identity}"
|
34
33
|
resource._fog_collection_tracker = self
|
35
34
|
new_collection << resource
|
36
|
-
|
35
|
+
#@log.debug "Got resource: #{resource.inspect}"
|
37
36
|
end
|
37
|
+
@log.info "Fetched #{new_collection.count} #{@type} on #{@account_name}."
|
38
38
|
@collection = new_collection
|
39
39
|
end
|
40
40
|
|
41
41
|
# @return [Hash] a Hash of account information, slighly modified:
|
42
42
|
# a :name parameter is added, and the :credentials are removed
|
43
|
+
# :preceeding_update_time is also added
|
43
44
|
def clean_account_data
|
44
45
|
@clean_data ||= @account # generate this data only once per res
|
45
46
|
@clean_data[:name] = @account_name
|
46
47
|
@clean_data[:credentials] = Hash.new
|
48
|
+
@clean_data[:preceeding_update_time] = @account_tracker.preceeding_update_time
|
47
49
|
@clean_data
|
48
50
|
end
|
49
51
|
|
@@ -52,8 +52,8 @@ module FogTracker
|
|
52
52
|
results
|
53
53
|
end
|
54
54
|
|
55
|
-
#
|
56
|
-
# @return [String]
|
55
|
+
# returns a descriptive identifier unique to this resource
|
56
|
+
# @return [String] the type, identity, and account name of the resource
|
57
57
|
def tracker_description
|
58
58
|
type = (self.class.name.match(/::([^:]+)$/))[1]
|
59
59
|
"#{type} #{self.identity} in account #{tracker_account[:name]}"
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module FogTracker
|
2
2
|
module Query
|
3
|
-
# A class for
|
4
|
-
#
|
3
|
+
# A class for filtering the Fog resources for a set of {AccountTracker}s,
|
4
|
+
# based on a regular-expression-based query
|
5
5
|
class QueryProcessor
|
6
6
|
|
7
7
|
# The Regular Expression which all resource queries must match
|
@@ -16,7 +16,7 @@ module FogTracker
|
|
16
16
|
@log = options[:logger] || FogTracker.default_logger
|
17
17
|
end
|
18
18
|
|
19
|
-
# Uses the query string to filter
|
19
|
+
# Uses the query string to filter all polled resources
|
20
20
|
# for a desired subset
|
21
21
|
# @param [String] query a string used to filter for matching resources
|
22
22
|
# @return [Array <Fog::Model>] an Array of Resources, filtered by query
|
@@ -36,7 +36,7 @@ module FogTracker
|
|
36
36
|
# Returns an Array of 4 RegEx objeccts based on the +query_string+
|
37
37
|
# for matching [account name, service, provider, collection]
|
38
38
|
def parse_query(query_string)
|
39
|
-
|
39
|
+
#@log.debug "Parsing Query #{query_string}"
|
40
40
|
tokenize(query_string).map {|token| regex_from_token(token)}
|
41
41
|
end
|
42
42
|
|
data/lib/fog_tracker/tracker.rb
CHANGED
@@ -92,6 +92,13 @@ module FogTracker
|
|
92
92
|
results
|
93
93
|
end
|
94
94
|
|
95
|
+
# Returns the time of given account's most recent successful update
|
96
|
+
# @param [String] account_name the name of the account that was polled
|
97
|
+
# @return [Time] the time the account finished updating
|
98
|
+
def preceeding_update_time(account_name)
|
99
|
+
@trackers[account_name].preceeding_update_time
|
100
|
+
end
|
101
|
+
|
95
102
|
# Returns this tracker's logger, for changing logging dynamically
|
96
103
|
def logger ; @log end
|
97
104
|
|
data/lib/fog_tracker/version.rb
CHANGED
@@ -27,8 +27,8 @@ module FogTracker
|
|
27
27
|
it "exposes its logger" do
|
28
28
|
@tracker.log.should_not == nil
|
29
29
|
end
|
30
|
-
it "always
|
31
|
-
@tracker.account[:
|
30
|
+
it "always has a delay" do
|
31
|
+
@tracker.account[:delay].should be_an_instance_of(Fixnum)
|
32
32
|
end
|
33
33
|
|
34
34
|
describe '#connection' do
|
@@ -76,6 +76,12 @@ module FogTracker
|
|
76
76
|
(Proc.new { @tracker.update }).should raise_error
|
77
77
|
end
|
78
78
|
end
|
79
|
+
it "saves the time of it's next-to-last update as @preceeding_update_time" do
|
80
|
+
@tracker.update
|
81
|
+
@tracker.preceeding_update_time.should == nil
|
82
|
+
@tracker.update
|
83
|
+
@tracker.preceeding_update_time.should_not == nil
|
84
|
+
end
|
79
85
|
end
|
80
86
|
|
81
87
|
describe '#start' do
|
@@ -22,10 +22,17 @@ module FogTracker
|
|
22
22
|
@tracker.update
|
23
23
|
end
|
24
24
|
it "attaches account information to all resources" do
|
25
|
+
fake_connection = double('mock fog connection')
|
26
|
+
fake_connection.stub(:servers).and_return(
|
27
|
+
[Fog::Compute[:aws].servers.new])
|
28
|
+
now = Time.now
|
29
|
+
@account_tracker.stub(:connection).and_return fake_connection
|
30
|
+
@account_tracker.stub(:preceeding_update_time).and_return now
|
25
31
|
@tracker.update
|
26
32
|
@tracker.collection.each do |resource|
|
27
33
|
resource.tracker_account[:name].should == FAKE_ACCOUNT_NAME
|
28
34
|
resource.tracker_account[:credentials].should == {}
|
35
|
+
resource.tracker_account[:preceeding_update_time].should == now
|
29
36
|
end
|
30
37
|
end
|
31
38
|
end
|
@@ -66,6 +66,17 @@ module FogTracker
|
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
69
|
+
describe '#preceeding_update_time' do
|
70
|
+
context "when given an account name" do
|
71
|
+
it "returns time of the account's next-most-recent succesful update" do
|
72
|
+
@tracker.update
|
73
|
+
@tracker.preceeding_update_time(ACCOUNTS.keys.first).should == nil
|
74
|
+
@tracker.update
|
75
|
+
@tracker.preceeding_update_time(ACCOUNTS.keys.first).should_not == nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
69
80
|
describe '#all' do
|
70
81
|
WILDCARD_QUERY = '*::*::*::*'
|
71
82
|
before(:each) do
|
data/spec/support/_mocks.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fog_tracker
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-02-
|
12
|
+
date: 2012-02-18 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: fog
|
16
|
-
requirement: &
|
16
|
+
requirement: &70238491411280 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70238491411280
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rake
|
27
|
-
requirement: &
|
27
|
+
requirement: &70238491410860 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70238491410860
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: rspec
|
38
|
-
requirement: &
|
38
|
+
requirement: &70238491410440 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70238491410440
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: yard
|
49
|
-
requirement: &
|
49
|
+
requirement: &70238491410020 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70238491410020
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: guard
|
60
|
-
requirement: &
|
60
|
+
requirement: &70238491409600 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: '0'
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *70238491409600
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: guard-rspec
|
71
|
-
requirement: &
|
71
|
+
requirement: &70238491409180 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ! '>='
|
@@ -76,10 +76,10 @@ dependencies:
|
|
76
76
|
version: '0'
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *70238491409180
|
80
80
|
- !ruby/object:Gem::Dependency
|
81
81
|
name: ruby_gntp
|
82
|
-
requirement: &
|
82
|
+
requirement: &70238491408740 !ruby/object:Gem::Requirement
|
83
83
|
none: false
|
84
84
|
requirements:
|
85
85
|
- - ! '>='
|
@@ -87,7 +87,7 @@ dependencies:
|
|
87
87
|
version: '0'
|
88
88
|
type: :development
|
89
89
|
prerelease: false
|
90
|
-
version_requirements: *
|
90
|
+
version_requirements: *70238491408740
|
91
91
|
description: This gem peridically polls mutiple cloud computing services using the
|
92
92
|
fog gem, asynchronously updating the state of the resulting collections of Fog Resources.
|
93
93
|
email:
|
@@ -138,7 +138,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
138
138
|
version: '0'
|
139
139
|
segments:
|
140
140
|
- 0
|
141
|
-
hash: -
|
141
|
+
hash: -64177952162432192
|
142
142
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
143
143
|
none: false
|
144
144
|
requirements:
|
@@ -147,7 +147,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
147
147
|
version: '0'
|
148
148
|
segments:
|
149
149
|
- 0
|
150
|
-
hash: -
|
150
|
+
hash: -64177952162432192
|
151
151
|
requirements: []
|
152
152
|
rubyforge_project: fog_tracker
|
153
153
|
rubygems_version: 1.8.10
|