vault-usage-client 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +5 -0
- data/.yardopts +1 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +58 -0
- data/README.md +165 -0
- data/Rakefile +11 -0
- data/bin/vault-usage +37 -0
- data/lib/vault-usage-client/client.rb +205 -0
- data/lib/vault-usage-client/version.rb +8 -0
- data/lib/vault-usage-client.rb +16 -0
- data/test/client_test.rb +338 -0
- data/test/helper.rb +4 -0
- data/test/version_test.rb +9 -0
- data/vault-usage-client.gemspec +21 -0
- metadata +116 -0
data/.gitignore
ADDED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
-m markdown
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
GIT
|
2
|
+
remote: https://github.com/heroku/vault-test-tools.git
|
3
|
+
revision: 5eabd67610f279b81cadb803cee8ef704093a05d
|
4
|
+
specs:
|
5
|
+
vault-test-tools (0.2.2)
|
6
|
+
minitest
|
7
|
+
nokogiri
|
8
|
+
rack-perftools_profiler
|
9
|
+
rack-test
|
10
|
+
rdoc
|
11
|
+
redcarpet
|
12
|
+
turn
|
13
|
+
yard
|
14
|
+
|
15
|
+
PATH
|
16
|
+
remote: .
|
17
|
+
specs:
|
18
|
+
vault-usage-client (0.0.1)
|
19
|
+
colorize
|
20
|
+
excon
|
21
|
+
yajl-ruby
|
22
|
+
|
23
|
+
GEM
|
24
|
+
remote: http://rubygems.org/
|
25
|
+
specs:
|
26
|
+
ansi (1.4.3)
|
27
|
+
colorize (0.5.8)
|
28
|
+
excon (0.16.10)
|
29
|
+
json (1.7.6)
|
30
|
+
minitest (4.6.0)
|
31
|
+
nokogiri (1.5.6)
|
32
|
+
open4 (1.3.0)
|
33
|
+
perftools.rb (2.0.0)
|
34
|
+
rack (1.5.2)
|
35
|
+
rack-perftools_profiler (0.6.0)
|
36
|
+
open4 (~> 1.0)
|
37
|
+
perftools.rb (~> 2.0.0)
|
38
|
+
rack (~> 1.0)
|
39
|
+
rack-test (0.6.2)
|
40
|
+
rack (>= 1.0)
|
41
|
+
rake (10.0.3)
|
42
|
+
rdoc (3.12.1)
|
43
|
+
json (~> 1.4)
|
44
|
+
redcarpet (2.2.2)
|
45
|
+
rr (1.0.4)
|
46
|
+
turn (0.9.6)
|
47
|
+
ansi
|
48
|
+
yajl-ruby (1.1.0)
|
49
|
+
yard (0.8.4.1)
|
50
|
+
|
51
|
+
PLATFORMS
|
52
|
+
ruby
|
53
|
+
|
54
|
+
DEPENDENCIES
|
55
|
+
rake
|
56
|
+
rr
|
57
|
+
vault-test-tools (~> 0.2.2)!
|
58
|
+
vault-usage-client!
|
data/README.md
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
# Vault::Usage::Client
|
2
|
+
|
3
|
+
Client provides a Ruby API for interacting with the `Vault::Usage`
|
4
|
+
HTTP API.
|
5
|
+
|
6
|
+
## Setting up a development environment
|
7
|
+
|
8
|
+
Install dependencies and run the test suite:
|
9
|
+
|
10
|
+
bundle install --binstubs vendor/bin
|
11
|
+
rbenv rehash
|
12
|
+
rake
|
13
|
+
|
14
|
+
Run tests:
|
15
|
+
|
16
|
+
rake test
|
17
|
+
|
18
|
+
See tasks:
|
19
|
+
|
20
|
+
rake -T
|
21
|
+
|
22
|
+
Generate API documentation:
|
23
|
+
|
24
|
+
rake yard
|
25
|
+
|
26
|
+
## Using the client from the command-line
|
27
|
+
|
28
|
+
The `bin/vault-usage` command-line tool can be used to quickly see
|
29
|
+
usage data for a particular user:
|
30
|
+
|
31
|
+
```bash
|
32
|
+
export VAULT_USAGE_URL=https://username:secret@vault-usage.herokuapp.com
|
33
|
+
bin/vault-usage user123@heroku.com 2013-01-01 2013-02-01
|
34
|
+
```
|
35
|
+
|
36
|
+
## Using the client in Ruby
|
37
|
+
|
38
|
+
The `Vault::Usage` API may only be accessed with valid credentials.
|
39
|
+
You must supply these when you create a new client:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
require 'vault-usage-client'
|
43
|
+
|
44
|
+
client = Vault::Usage::Client::Client.new(
|
45
|
+
'https://username:secret@vault-usage.herokuapp.com')
|
46
|
+
```
|
47
|
+
|
48
|
+
### Usage events
|
49
|
+
|
50
|
+
Usage events represent usage of a product, for a period of time, by an
|
51
|
+
app or user.
|
52
|
+
|
53
|
+
#### Opening a usage event
|
54
|
+
|
55
|
+
Each usage event must have a unique UUID provided by the service
|
56
|
+
reporting it and the start time must be in UTC:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
event_id = SecureRandom.uuid
|
60
|
+
product_name = 'platform:dyno:logical'
|
61
|
+
consumer_hid = 'app1234@heroku.com'
|
62
|
+
start_time = Time.now.getutc
|
63
|
+
client.open_usage_event(event_id, product_name, consumer_hid, start_time)
|
64
|
+
```
|
65
|
+
|
66
|
+
Arbitrary data related to the usage event can optionally be provided
|
67
|
+
by way of a detail object:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
event_id = SecureRandom.uuid
|
71
|
+
product_name = 'platform:dyno:logical'
|
72
|
+
consumer_hid = 'app1234@heroku.com'
|
73
|
+
start_time = Time.now.getutc
|
74
|
+
detail = {type: 'web',
|
75
|
+
description: 'bundle exec bin/web',
|
76
|
+
kernel: 'us-east-1-a'}
|
77
|
+
client.open_usage_event(event_id, product_name, consumer_hid, start_time,
|
78
|
+
detail)
|
79
|
+
```
|
80
|
+
|
81
|
+
Keys in the detail object must be of type `Symbol` and values may only
|
82
|
+
be of type `String`, `Fixnum`, `Bignum`, `Float`, `TrueClass`,
|
83
|
+
`FalseClass` or `NilClass`. In other words, it's not possible to use
|
84
|
+
nested structures in the detail object.
|
85
|
+
|
86
|
+
#### Closing a usage event
|
87
|
+
|
88
|
+
Closing a usage event works the same way as opening one:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
event_id = SecureRandom.uuid
|
92
|
+
product_name = 'platform:dyno:logical'
|
93
|
+
consumer_hid = 'app1234@heroku.com'
|
94
|
+
stop_time = Time.now.getutc
|
95
|
+
client.close_event(event_id, product_name, consumer_hid, stop_time)
|
96
|
+
```
|
97
|
+
|
98
|
+
#### Retrieving usage information
|
99
|
+
|
100
|
+
Usage information for a particular user can be retrieved by the
|
101
|
+
client. The start and stop time must both be specified in UTC:
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
user_hid = 'user1234@heroku.com'
|
105
|
+
start_time = Time.utc(2013, 1)
|
106
|
+
stop_time = Time.utc(2013, 2)
|
107
|
+
events = client.usage_for_user(user_hid, start_time, stop_time)
|
108
|
+
```
|
109
|
+
|
110
|
+
The `events` result is an `Array` of objects matching this format:
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
[{id: '<event-uuid>',
|
114
|
+
product: '<name>',
|
115
|
+
consumer: '<consumer-hid>',
|
116
|
+
start_time: <Time>,
|
117
|
+
stop_time: <Time>,
|
118
|
+
detail: {<key1>: <value1>,
|
119
|
+
<key2>: <value2>,
|
120
|
+
...}},
|
121
|
+
...]}
|
122
|
+
```
|
123
|
+
|
124
|
+
In some cases it can be useful to exclude event data for certain
|
125
|
+
products:
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
user_hid = 'user1234@heroku.com'
|
129
|
+
start_time = Time.utc(2013, 1)
|
130
|
+
stop_time = Time.utc(2013, 2)
|
131
|
+
events = client.usage_for_user(user_hid, start_time, stop_time,
|
132
|
+
['platform:dyno:physical'])
|
133
|
+
```
|
134
|
+
|
135
|
+
You can pass one or more product names to exclude.
|
136
|
+
|
137
|
+
### App ownership events
|
138
|
+
|
139
|
+
App ownership events represent ownership of an app, for a period of
|
140
|
+
time, by a user.
|
141
|
+
|
142
|
+
#### Opening an app ownership event
|
143
|
+
|
144
|
+
Each app ownership event must have a unique UUID provided by the
|
145
|
+
service reporting it and the start time must be in UTC:
|
146
|
+
|
147
|
+
```ruby
|
148
|
+
event_id = SecureRandom.uuid
|
149
|
+
user_hid = 'user1234@heroku.com'
|
150
|
+
app_hid = 'app1234@heroku.com'
|
151
|
+
start_time = Time.now.getutc
|
152
|
+
client.open_app_ownership_event(event_id, user_hid, app_hid, start_time)
|
153
|
+
```
|
154
|
+
|
155
|
+
#### Closing an app ownership event
|
156
|
+
|
157
|
+
Closing an app ownership event works the same way as opening one:
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
event_id = SecureRandom.uuid
|
161
|
+
user_hid = 'user1234@heroku.com'
|
162
|
+
app_hid = 'app1234@heroku.com'
|
163
|
+
stop_time = Time.now.getutc
|
164
|
+
client.close_app_ownership_event(event_id, user_hid, app_hid, stop_time)
|
165
|
+
```
|
data/Rakefile
ADDED
data/bin/vault-usage
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
lib = File.expand_path('../../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
|
6
|
+
require 'vault-usage-client'
|
7
|
+
require 'colorize'
|
8
|
+
|
9
|
+
user_hid = ARGV[0]
|
10
|
+
start_time = Time.utc(*ARGV[1].split('-'))
|
11
|
+
stop_time = Time.utc(*ARGV[2].split('-'))
|
12
|
+
|
13
|
+
client = Vault::Usage::Client.new
|
14
|
+
|
15
|
+
all_events = client.usage_for_user(user_hid, start_time, stop_time, ['platform:dyno:physical'])[:events]
|
16
|
+
|
17
|
+
all_events.group_by { |e| e[:consumer] }.each do |consumer, app_events|
|
18
|
+
puts "=== #{consumer} ===".blue
|
19
|
+
|
20
|
+
app_events.group_by { |e| e[:product] }.each do |product, product_events|
|
21
|
+
|
22
|
+
puts " #{product}"
|
23
|
+
product_events.
|
24
|
+
map { |event| OpenStruct.new(event) }.
|
25
|
+
sort_by(&:start_time).
|
26
|
+
each do |e|
|
27
|
+
puts " #{e.id}".light_green
|
28
|
+
puts " #{e.start_time} -> #{e.stop_time}: #{e.detail}".green
|
29
|
+
if e.start_time > e.stop_time
|
30
|
+
puts " ERROR: start_time is after stop_time".red
|
31
|
+
end
|
32
|
+
puts
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
puts
|
37
|
+
end
|
@@ -0,0 +1,205 @@
|
|
1
|
+
module Vault::Usage
|
2
|
+
# Client for the `Vault::Usage` HTTP API.
|
3
|
+
class Client
|
4
|
+
attr_reader :url
|
5
|
+
|
6
|
+
# Raised if a non-UTC time is used with the client.
|
7
|
+
class InvalidTimeError < Exception
|
8
|
+
end
|
9
|
+
|
10
|
+
# Instantiate a client.
|
11
|
+
#
|
12
|
+
# @param url [String] The URL to connect to. Include the username and
|
13
|
+
# password to use when connecting.
|
14
|
+
def initialize(url = nil)
|
15
|
+
@url = url || ENV['VAULT_USAGE_URL']
|
16
|
+
end
|
17
|
+
|
18
|
+
# Report that usage of a product, by a user or app, started at a
|
19
|
+
# particular time.
|
20
|
+
#
|
21
|
+
# @param event_id [String] A UUID that uniquely identifies the usage
|
22
|
+
# event.
|
23
|
+
# @param product_name [String] The name of the product that was used, such
|
24
|
+
# as `platform:dyno:logical` or `addon:memcache:100mb`.
|
25
|
+
# @param consumer_hid [String] The Heroku ID, such as `app1234@heroku.com`
|
26
|
+
# or `user1234@heroku.com`, that represents the user or app that used
|
27
|
+
# the specified product.
|
28
|
+
# @param start_time [Time] The beginning of the usage period, always in
|
29
|
+
# UTC.
|
30
|
+
# @param detail [Hash] Optionally, additional details to store with the
|
31
|
+
# event. Keys must be of type `Symbol` and values may only be of type
|
32
|
+
# `String`, `Fixnum`, `Bignum`, `Float`, `TrueClass`, `FalseClass` or
|
33
|
+
# `NilClass`.
|
34
|
+
# @raise [InvalidTimeError] Raised if a non-UTC start time is provided.
|
35
|
+
# @raise [Excon::Errors::HTTPStatusError] Raised if the server returns an
|
36
|
+
# unsuccessful HTTP status code.
|
37
|
+
def open_usage_event(event_id, product_name, consumer_hid, start_time,
|
38
|
+
detail=nil)
|
39
|
+
unless start_time.zone.eql?('UTC')
|
40
|
+
raise InvalidTimeError.new('Start time must be in UTC.')
|
41
|
+
end
|
42
|
+
path = "/products/#{product_name}/usage/#{consumer_hid}" +
|
43
|
+
"/events/#{event_id}/open/#{iso_format(start_time)}"
|
44
|
+
unless detail.nil?
|
45
|
+
headers = {'Content-Type' => 'application/json'}
|
46
|
+
body = JSON.generate(detail)
|
47
|
+
end
|
48
|
+
connection = Excon.new(@url)
|
49
|
+
connection.put(path: path, headers: headers, body: body,
|
50
|
+
expects: [201])
|
51
|
+
end
|
52
|
+
|
53
|
+
# Report that usage of a product, by a user or app, stopped at a
|
54
|
+
# particular time.
|
55
|
+
#
|
56
|
+
# @param event_id [String] A UUID that uniquely identifies the usage
|
57
|
+
# event.
|
58
|
+
# @param product_name [String] The name of the product that was used, such
|
59
|
+
# as `platform:dyno:logical` or `addon:memcache:100mb`.
|
60
|
+
# @param consumer_hid [String] The Heroku ID, such as `app1234@heroku.com`
|
61
|
+
# or `user1234@heroku.com`, that represents the user or app that used
|
62
|
+
# the specified product.
|
63
|
+
# @param stop_time [Time] The end of the usage period, always in UTC.
|
64
|
+
# @raise [InvalidTimeError] Raised if a non-UTC stop time is provided.
|
65
|
+
# @raise [Excon::Errors::HTTPStatusError] Raised if the server returns an
|
66
|
+
# unsuccessful HTTP status code.
|
67
|
+
def close_usage_event(event_id, product_name, consumer_hid, stop_time)
|
68
|
+
unless stop_time.zone.eql?('UTC')
|
69
|
+
raise InvalidTimeError.new('Stop time must be in UTC.')
|
70
|
+
end
|
71
|
+
path = "/products/#{product_name}/usage/#{consumer_hid}" +
|
72
|
+
"/events/#{event_id}/close/#{iso_format(stop_time)}"
|
73
|
+
connection = Excon.new(@url)
|
74
|
+
connection.put(path: path, expects: [201])
|
75
|
+
end
|
76
|
+
|
77
|
+
# Get the usage events for the apps owned by the specified user during the
|
78
|
+
# specified period.
|
79
|
+
#
|
80
|
+
# @param user_hid [String] The user HID, such as `user1234@heroku.com`, to
|
81
|
+
# fetch usage data for.
|
82
|
+
# @param start_time [Time] The beginning of the usage period, always in
|
83
|
+
# UTC, within which events must overlap to be included in usage data.
|
84
|
+
# @param stop_time [Time] The end of the usage period, always in UTC,
|
85
|
+
# within which events must overlap to be included in usage data.
|
86
|
+
# @param exclude [Array] Optionally, a list of product names, such as
|
87
|
+
# `['platform:dyno:physical', 'addon:memcache:100mb']`, to be excluded
|
88
|
+
# from usage data.
|
89
|
+
# @raise [InvalidTimeError] Raised if a non-UTC start or stop time is
|
90
|
+
# provided.
|
91
|
+
# @raise [Excon::Errors::HTTPStatusError] Raised if the server returns an
|
92
|
+
# unsuccessful HTTP status code.
|
93
|
+
# @return [Array] A list of usage events for the specified user, matching
|
94
|
+
# the following format:
|
95
|
+
#
|
96
|
+
# ```
|
97
|
+
# [{id: '<event-uuid>',
|
98
|
+
# product: '<name>',
|
99
|
+
# consumer: '<heroku-id>',
|
100
|
+
# start_time: <Time>,
|
101
|
+
# stop_time: <Time>,
|
102
|
+
# detail: {<key1>: <value1>,
|
103
|
+
# <key2>: <value2>,
|
104
|
+
# ...}},
|
105
|
+
# ...]}
|
106
|
+
# ```
|
107
|
+
def usage_for_user(user_hid, start_time, stop_time, exclude=nil)
|
108
|
+
unless start_time.zone.eql?('UTC')
|
109
|
+
raise InvalidTimeError.new('Start time must be in UTC.')
|
110
|
+
end
|
111
|
+
unless stop_time.zone.eql?('UTC')
|
112
|
+
raise InvalidTimeError.new('Stop time must be in UTC.')
|
113
|
+
end
|
114
|
+
path = "/users/#{user_hid}/usage/#{iso_format(start_time)}/" +
|
115
|
+
"#{iso_format(stop_time)}"
|
116
|
+
unless exclude.nil? || exclude.empty?
|
117
|
+
query = {exclude: exclude.join(',')}
|
118
|
+
end
|
119
|
+
connection = Excon.new(@url)
|
120
|
+
response = connection.get(path: path, expects: [200], query: query)
|
121
|
+
events = JSON.parse(response.body, {symbolize_keys: true})
|
122
|
+
events.each do |event|
|
123
|
+
event.each do |key, value|
|
124
|
+
event[key] = parse_date(value) if date?(value)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Report that ownership of an app by a user started at a particular time.
|
130
|
+
#
|
131
|
+
# @param event_id [String] A UUID that uniquely identifies the ownership
|
132
|
+
# event.
|
133
|
+
# @param user_hid [String] The user HID, such as `user1234@heroku.com`,
|
134
|
+
# that owns the specified app.
|
135
|
+
# @param app_hid [String] The app HID, such as `app1234@heroku.com`, that
|
136
|
+
# is being transferred to the specified user.
|
137
|
+
# @param start_time [Time] The beginning of the ownership period, always
|
138
|
+
# in UTC.
|
139
|
+
# @raise [InvalidTimeError] Raised if a non-UTC start time is provided.
|
140
|
+
# @raise [Excon::Errors::HTTPStatusError] Raised if the server returns an
|
141
|
+
# unsuccessful HTTP status code.
|
142
|
+
def open_app_ownership_event(event_id, user_hid, app_hid, start_time)
|
143
|
+
unless start_time.zone.eql?('UTC')
|
144
|
+
raise InvalidTimeError.new('Start time must be in UTC.')
|
145
|
+
end
|
146
|
+
path = "/users/#{user_hid}/apps/#{app_hid}/open/#{event_id}" +
|
147
|
+
"/#{iso_format(start_time)}"
|
148
|
+
connection = Excon.new(@url)
|
149
|
+
connection.put(path: path, expects: [201])
|
150
|
+
end
|
151
|
+
|
152
|
+
# Report that ownership of an app by a user stopped at a particular time.
|
153
|
+
#
|
154
|
+
# @param event_id [String] A UUID that uniquely identifies the ownership
|
155
|
+
# event.
|
156
|
+
# @param user_hid [String] The user HID, such as `user1234@heroku.com`,
|
157
|
+
# that owned the specified app.
|
158
|
+
# @param app_hid [String] The app HID, such as `app1234@heroku.com`, that
|
159
|
+
# is being transferred away from the specified user.
|
160
|
+
# @param stop_time [Time] The end of the ownership period, always in UTC.
|
161
|
+
# @raise [InvalidTimeError] Raised if a non-UTC stop time is provided.
|
162
|
+
# @raise [Excon::Errors::HTTPStatusError] Raised if the server returns an
|
163
|
+
# unsuccessful HTTP status code.
|
164
|
+
def close_app_ownership_event(event_id, user_hid, app_hid, stop_time)
|
165
|
+
unless stop_time.zone.eql?('UTC')
|
166
|
+
raise InvalidTimeError.new('Stop time must be in UTC.')
|
167
|
+
end
|
168
|
+
path = "/users/#{user_hid}/apps/#{app_hid}/close/#{event_id}" +
|
169
|
+
"/#{iso_format(stop_time)}"
|
170
|
+
connection = Excon.new(@url)
|
171
|
+
connection.put(path: path, expects: [201])
|
172
|
+
end
|
173
|
+
|
174
|
+
private
|
175
|
+
|
176
|
+
# Convert a time to an ISO 8601 combined data and time format.
|
177
|
+
#
|
178
|
+
# @param time [Time] The time to convert to ISO 8601 format.
|
179
|
+
# @return [String] An ISO 8601 date in `YYYY-MM-DDTHH:MM:SSZ` format.
|
180
|
+
def iso_format(time)
|
181
|
+
time.strftime('%Y-%m-%dT%H:%M:%SZ')
|
182
|
+
end
|
183
|
+
|
184
|
+
# Determine if the value is an ISO 8601 date in `YYYY-MM-DDTHH:MM:SSZ`
|
185
|
+
# format.
|
186
|
+
#
|
187
|
+
# @param value [String] The value to check for date-ness.
|
188
|
+
# @return [TrueClass,FalseClass] True if the value resembles a date,
|
189
|
+
# otherwise false.
|
190
|
+
def date?(value)
|
191
|
+
value =~ /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z/
|
192
|
+
end
|
193
|
+
|
194
|
+
# Parse an ISO 8601 date in `YYYY-MM-DDTHH:MM:SSZ` format into a Time
|
195
|
+
# instance.
|
196
|
+
#
|
197
|
+
# @param value [String] The value to parse.
|
198
|
+
# @raise [ArgumentError] Raised if the value doesn't represent a valid
|
199
|
+
# date.
|
200
|
+
# @return [Time] The Time instance created from the date string in UTC.
|
201
|
+
def parse_date(value)
|
202
|
+
DateTime.parse(value).to_time.getutc
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler.require :default, ENV['RACK_ENV'].to_sym
|
3
|
+
|
4
|
+
require 'excon'
|
5
|
+
require 'yajl/json_gem'
|
6
|
+
|
7
|
+
module Vault
|
8
|
+
module Usage
|
9
|
+
# Client provides a Ruby API to access the Vault::Usage HTTP API.
|
10
|
+
class Client
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
require 'vault-usage-client/client'
|
16
|
+
require 'vault-usage-client/version'
|
data/test/client_test.rb
ADDED
@@ -0,0 +1,338 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class ClientTest < Vault::TestCase
|
4
|
+
include Vault::Test::EnvironmentHelpers
|
5
|
+
|
6
|
+
def setup
|
7
|
+
super
|
8
|
+
Excon.stubs.clear
|
9
|
+
Excon.defaults[:mock] = true
|
10
|
+
@client = Vault::Usage::Client.new(
|
11
|
+
'https://username:secret@vault-usage.herokuapp.com')
|
12
|
+
@event_id = 'd8bb95d1-6279-4488-961d-133514b772fa'
|
13
|
+
@product_name = 'platform:dyno:logical'
|
14
|
+
@app_hid = 'app123@heroku.com'
|
15
|
+
@user_hid = 'user456@heroku.com'
|
16
|
+
@start_time = Time.utc(2013, 1)
|
17
|
+
@stop_time = Time.utc(2013, 2)
|
18
|
+
end
|
19
|
+
|
20
|
+
def teardown
|
21
|
+
# FIXME This is a bit ugly, but Excon doesn't provide a builtin way to
|
22
|
+
# ensure that a request was invoked, so we have to do it ourselves.
|
23
|
+
# Without this, and the Excon.stubs.pop calls in the tests below, tests
|
24
|
+
# will pass if request logic is completely removed from application
|
25
|
+
# code. -jkakar
|
26
|
+
assert(Excon.stubs.empty?, 'Expected HTTP requests were not made.')
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
# Convert a time to an ISO 8601 combined data and time format.
|
31
|
+
def iso_format(time)
|
32
|
+
time.strftime('%Y-%m-%dT%H:%M:%SZ')
|
33
|
+
end
|
34
|
+
|
35
|
+
# Client.new looks for VAULT_USAGE_URL if none is passed in to the constructor
|
36
|
+
def test_uses_env_for_url_if_not_given
|
37
|
+
url = 'http://foo:bar@example.com'
|
38
|
+
set_env 'VAULT_USAGE_URL', url
|
39
|
+
@client = Vault::Usage::Client.new
|
40
|
+
assert_equal(url, @client.url)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Client.open_usage_event makes a PUT request to the Vault::Usage HTTP API,
|
44
|
+
# passing the supplied credentials using HTTP basic auth, to report that
|
45
|
+
# usage of a product began at a particular time.
|
46
|
+
def test_open_usage_event
|
47
|
+
Excon.stub(method: :put) do |request|
|
48
|
+
assert_equal('Basic dXNlcm5hbWU6c2VjcmV0',
|
49
|
+
request[:headers]['Authorization'])
|
50
|
+
assert_equal('vault-usage.herokuapp.com:443', request[:host_port])
|
51
|
+
assert_equal("/products/#{@product_name}/usage/#{@app_hid}" +
|
52
|
+
"/events/#{@event_id}/open/#{iso_format(@start_time)}",
|
53
|
+
request[:path])
|
54
|
+
Excon.stubs.pop
|
55
|
+
{status: 201}
|
56
|
+
end
|
57
|
+
@client.open_usage_event(@event_id, @product_name, @app_hid, @start_time)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Client.open_usage_event optionally accepts a detail hash which is sent as
|
61
|
+
# a JSON payload in the request body when provided.
|
62
|
+
def test_open_usage_event_with_detail
|
63
|
+
detail = {type: 'web',
|
64
|
+
description: 'bundle exec bin/web',
|
65
|
+
kernel: 'us-east-1-a'}
|
66
|
+
Excon.stub(method: :put) do |request|
|
67
|
+
assert_equal('application/json', request[:headers]['Content-Type'])
|
68
|
+
assert_equal(detail, JSON.parse(request[:body], {symbolize_keys: true}))
|
69
|
+
Excon.stubs.pop
|
70
|
+
{status: 201}
|
71
|
+
end
|
72
|
+
@client.open_usage_event(@event_id, @product_name, @app_hid, @start_time,
|
73
|
+
detail)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Client.open_usage_event raises an InvalidTimeError if the start time is
|
77
|
+
# not in UTC.
|
78
|
+
def test_open_usage_event_with_non_utc_start_time
|
79
|
+
start_time = Time.new(2013, 1, 12, 15, 25, 0, '+09:00')
|
80
|
+
error = assert_raises Vault::Usage::Client::InvalidTimeError do
|
81
|
+
@client.open_usage_event(@event_id, @product_name, @app_hid, start_time)
|
82
|
+
end
|
83
|
+
assert_equal('Start time must be in UTC.', error.message)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Client.open_usage_event raises the appropriate
|
87
|
+
# Excon::Errors::HTTPStatusError if an unsuccessful HTTP status code is
|
88
|
+
# returned by the server.
|
89
|
+
def test_open_usage_event_with_unsuccessful_response
|
90
|
+
Excon.stub(method: :put) do |request|
|
91
|
+
Excon.stubs.pop
|
92
|
+
{status: 400, body: 'Bad inputs provided.'}
|
93
|
+
end
|
94
|
+
assert_raises Excon::Errors::BadRequest do
|
95
|
+
@client.open_usage_event(@event_id, @product_name, @app_hid, @start_time)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Client.close_usage_event makes a PUT request to the Vault::Usage HTTP
|
100
|
+
# API, passing the supplied credentials using HTTP basic auth, to report
|
101
|
+
# that usage of a product ended at a particular time.
|
102
|
+
def test_close_usage_event
|
103
|
+
Excon.stub(method: :put) do |request|
|
104
|
+
assert_equal('Basic dXNlcm5hbWU6c2VjcmV0',
|
105
|
+
request[:headers]['Authorization'])
|
106
|
+
assert_equal('vault-usage.herokuapp.com:443', request[:host_port])
|
107
|
+
assert_equal("/products/#{@product_name}/usage/#{@app_hid}" +
|
108
|
+
"/events/#{@event_id}/close/#{iso_format(@stop_time)}",
|
109
|
+
request[:path])
|
110
|
+
Excon.stubs.pop
|
111
|
+
{status: 201}
|
112
|
+
end
|
113
|
+
@client.close_usage_event(@event_id, @product_name, @app_hid, @stop_time)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Client.close_usage_event raises an InvalidTimeError if the stop time is
|
117
|
+
# not in UTC.
|
118
|
+
def test_close_usage_event_with_non_utc_stop_time
|
119
|
+
stop_time = Time.new(2013, 1, 12, 15, 25, 0, '+09:00')
|
120
|
+
error = assert_raises Vault::Usage::Client::InvalidTimeError do
|
121
|
+
@client.close_usage_event(@event_id, @product_name, @app_hid, stop_time)
|
122
|
+
end
|
123
|
+
assert_equal('Stop time must be in UTC.', error.message)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Client.close_usage_event raises the appropriate
|
127
|
+
# Excon::Errors::HTTPStatusError if an unsuccessful HTTP status code is
|
128
|
+
# returned by the server.
|
129
|
+
def test_close_usage_event_with_unsuccessful_response
|
130
|
+
Excon.stub(method: :put) do |request|
|
131
|
+
Excon.stubs.pop
|
132
|
+
{status: 400, body: 'Bad inputs provided.'}
|
133
|
+
end
|
134
|
+
assert_raises Excon::Errors::BadRequest do
|
135
|
+
@client.close_usage_event(@event_id, @product_name, @app_hid, @stop_time)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Client.usage_for_user makes a GET request to the Vault::Usage HTTP API,
|
140
|
+
# passing the supplied credentials using HTTP basic auth, to retrieve the
|
141
|
+
# usage events for a particular user that occurred during the specified
|
142
|
+
# period of time.
|
143
|
+
def test_usage_for_user
|
144
|
+
Excon.stub(method: :get) do |request|
|
145
|
+
assert_equal('Basic dXNlcm5hbWU6c2VjcmV0',
|
146
|
+
request[:headers]['Authorization'])
|
147
|
+
assert_equal('vault-usage.herokuapp.com:443', request[:host_port])
|
148
|
+
assert_equal("/users/#{@user_hid}/usage/#{iso_format(@start_time)}/" +
|
149
|
+
"#{iso_format(@stop_time)}",
|
150
|
+
request[:path])
|
151
|
+
Excon.stubs.pop
|
152
|
+
{status: 200, body: JSON.generate([])}
|
153
|
+
end
|
154
|
+
assert_equal([], @client.usage_for_user(@user_hid, @start_time,
|
155
|
+
@stop_time))
|
156
|
+
end
|
157
|
+
|
158
|
+
# Client.usage_for_user optionally accepts a list of products to exclude.
|
159
|
+
# They're passed to the server using query string arguments.
|
160
|
+
def test_usage_for_user_with_exclude
|
161
|
+
Excon.stub(method: :get) do |request|
|
162
|
+
assert_equal({exclude: 'platform:dyno:physical'}, request[:query])
|
163
|
+
Excon.stubs.pop
|
164
|
+
{status: 200, body: JSON.generate([])}
|
165
|
+
end
|
166
|
+
assert_equal([], @client.usage_for_user(@user_hid, @start_time, @stop_time,
|
167
|
+
['platform:dyno:physical']))
|
168
|
+
end
|
169
|
+
|
170
|
+
# Client.usage_for_user comma-separates product names, when many are
|
171
|
+
# provided in the exclusion list, and passes them to the server using a
|
172
|
+
# single query argument.
|
173
|
+
def test_usage_for_user_with_many_excludes
|
174
|
+
Excon.stub(method: :get) do |request|
|
175
|
+
assert_equal({exclude: 'platform:dyno:physical,addons:memcache:100mb'},
|
176
|
+
request[:query])
|
177
|
+
Excon.stubs.pop
|
178
|
+
{status: 200, body: JSON.generate([])}
|
179
|
+
end
|
180
|
+
assert_equal([], @client.usage_for_user(@user_hid, @start_time, @stop_time,
|
181
|
+
['platform:dyno:physical',
|
182
|
+
'addons:memcache:100mb']))
|
183
|
+
end
|
184
|
+
|
185
|
+
# Client.usage_for_user with an empty product exclusion list is the same as
|
186
|
+
# not passing one at all.
|
187
|
+
def test_usage_for_user_with_empty_exclude
|
188
|
+
Excon.stub(method: :get) do |request|
|
189
|
+
assert_equal(nil, request[:query])
|
190
|
+
Excon.stubs.pop
|
191
|
+
{status: 200, body: JSON.generate([])}
|
192
|
+
end
|
193
|
+
assert_equal([], @client.usage_for_user(@user_hid, @start_time, @stop_time,
|
194
|
+
[]))
|
195
|
+
end
|
196
|
+
|
197
|
+
# Client.usage_for_user converts event start and stop times to Time
|
198
|
+
# instances.
|
199
|
+
def test_usage_for_user_converts_times
|
200
|
+
Excon.stub(method: :get) do |request|
|
201
|
+
Excon.stubs.pop
|
202
|
+
events = [{id: @event_id,
|
203
|
+
product: @product_name,
|
204
|
+
consumer: @app_hid,
|
205
|
+
start_time: iso_format(@start_time),
|
206
|
+
stop_time: iso_format(@stop_time),
|
207
|
+
detail: {}}]
|
208
|
+
{status: 200, body: JSON.generate(events)}
|
209
|
+
end
|
210
|
+
assert_equal([{id: @event_id,
|
211
|
+
product: @product_name,
|
212
|
+
consumer: @app_hid,
|
213
|
+
start_time: @start_time,
|
214
|
+
stop_time: @stop_time,
|
215
|
+
detail: {}}],
|
216
|
+
@client.usage_for_user(@user_hid, @start_time, @stop_time))
|
217
|
+
end
|
218
|
+
|
219
|
+
# Client.usage_for_user raises an InvalidTimeError if the start time is not
|
220
|
+
# in UTC.
|
221
|
+
def test_usage_for_user_with_non_utc_start_time
|
222
|
+
start_time = Time.new(2013, 1, 12, 15, 25, 0, '+09:00')
|
223
|
+
error = assert_raises Vault::Usage::Client::InvalidTimeError do
|
224
|
+
@client.usage_for_user(@user_hid, start_time, @stop_time)
|
225
|
+
end
|
226
|
+
assert_equal('Start time must be in UTC.', error.message)
|
227
|
+
end
|
228
|
+
|
229
|
+
# Client.usage_for_user raises an InvalidTimeError if the stop time is not
|
230
|
+
# in UTC.
|
231
|
+
def test_usage_for_user_with_non_utc_stop_time
|
232
|
+
stop_time = Time.new(2013, 1, 12, 15, 25, 0, '+09:00')
|
233
|
+
error = assert_raises Vault::Usage::Client::InvalidTimeError do
|
234
|
+
@client.usage_for_user(@user_hid, @start_time, stop_time)
|
235
|
+
end
|
236
|
+
assert_equal('Stop time must be in UTC.', error.message)
|
237
|
+
end
|
238
|
+
|
239
|
+
# Client.usage_for_user raises the appropriate Excon::Errors::HTTPStatusError
|
240
|
+
# if an unsuccessful HTTP status code is returned by the server.
|
241
|
+
def test_usage_for_user_with_unsuccessful_response
|
242
|
+
Excon.stub(method: :get) do |request|
|
243
|
+
Excon.stubs.pop
|
244
|
+
{status: 400, body: 'Bad inputs provided.'}
|
245
|
+
end
|
246
|
+
assert_raises Excon::Errors::BadRequest do
|
247
|
+
@client.usage_for_user(@user_hid, @start_time, @stop_time)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
# Client.open_app_ownership_event makes a PUT request to the Vault::Usage
|
252
|
+
# HTTP API, passing the supplied credentials using HTTP basic auth, to
|
253
|
+
# report that ownership of an app, by a particular user, began at a
|
254
|
+
# particular time.
|
255
|
+
def test_open_app_ownership_event
|
256
|
+
Excon.stub(method: :put) do |request|
|
257
|
+
assert_equal('Basic dXNlcm5hbWU6c2VjcmV0',
|
258
|
+
request[:headers]['Authorization'])
|
259
|
+
assert_equal('vault-usage.herokuapp.com:443', request[:host_port])
|
260
|
+
assert_equal("/users/#{@user_hid}/apps/#{@app_hid}/open/#{@event_id}" +
|
261
|
+
"/#{iso_format(@start_time)}",
|
262
|
+
request[:path])
|
263
|
+
Excon.stubs.pop
|
264
|
+
{status: 201}
|
265
|
+
end
|
266
|
+
@client.open_app_ownership_event(@event_id, @user_hid, @app_hid,
|
267
|
+
@start_time)
|
268
|
+
end
|
269
|
+
|
270
|
+
# Client.open_app_ownership_event raises an InvalidTimeError if the start
|
271
|
+
# time is not in UTC.
|
272
|
+
def test_open_app_ownership_event_with_non_utc_start_time
|
273
|
+
start_time = Time.new(2013, 1, 12, 15, 25, 0, '+09:00')
|
274
|
+
error = assert_raises Vault::Usage::Client::InvalidTimeError do
|
275
|
+
@client.open_app_ownership_event(@event_id, @user_hid, @app_hid,
|
276
|
+
start_time)
|
277
|
+
end
|
278
|
+
assert_equal('Start time must be in UTC.', error.message)
|
279
|
+
end
|
280
|
+
|
281
|
+
# Client.open_app_ownership_event raises the appropriate
|
282
|
+
# Excon::Errors::HTTPStatusError if an unsuccessful HTTP status code is
|
283
|
+
# returned by the server.
|
284
|
+
def test_open_app_ownership_event_with_unsuccessful_response
|
285
|
+
Excon.stub(method: :put) do |request|
|
286
|
+
Excon.stubs.pop
|
287
|
+
{status: 400, body: 'Bad inputs provided.'}
|
288
|
+
end
|
289
|
+
assert_raises Excon::Errors::BadRequest do
|
290
|
+
@client.open_app_ownership_event(@event_id, @user_hid, @app_hid,
|
291
|
+
@start_time)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
# Client.close_app_ownership_event makes a PUT request to the Vault::Usage
|
296
|
+
# HTTP API, passing the supplied credentials using HTTP basic auth, to
|
297
|
+
# report that ownership of an app, by a particular user, began at a
|
298
|
+
# particular time.
|
299
|
+
def test_close_app_ownership_event
|
300
|
+
Excon.stub(method: :put) do |request|
|
301
|
+
assert_equal('Basic dXNlcm5hbWU6c2VjcmV0',
|
302
|
+
request[:headers]['Authorization'])
|
303
|
+
assert_equal('vault-usage.herokuapp.com:443', request[:host_port])
|
304
|
+
assert_equal("/users/#{@user_hid}/apps/#{@app_hid}/close/#{@event_id}" +
|
305
|
+
"/#{iso_format(@stop_time)}",
|
306
|
+
request[:path])
|
307
|
+
Excon.stubs.pop
|
308
|
+
{status: 201}
|
309
|
+
end
|
310
|
+
@client.close_app_ownership_event(@event_id, @user_hid, @app_hid,
|
311
|
+
@stop_time)
|
312
|
+
end
|
313
|
+
|
314
|
+
# Client.close_app_ownership_event raises an InvalidTimeError if the stop
|
315
|
+
# time is not in UTC.
|
316
|
+
def test_close_app_ownership_event_with_non_utc_stop_time
|
317
|
+
stop_time = Time.new(2013, 1, 12, 15, 25, 0, '+09:00')
|
318
|
+
error = assert_raises Vault::Usage::Client::InvalidTimeError do
|
319
|
+
@client.close_app_ownership_event(@event_id, @user_hid, @app_hid,
|
320
|
+
stop_time)
|
321
|
+
end
|
322
|
+
assert_equal('Stop time must be in UTC.', error.message)
|
323
|
+
end
|
324
|
+
|
325
|
+
# Client.close_app_ownership_event raises the appropriate
|
326
|
+
# Excon::Errors::HTTPStatusError if an unsuccessful HTTP status code is
|
327
|
+
# returned by the server.
|
328
|
+
def test_close_app_ownership_event_with_unsuccessful_response
|
329
|
+
Excon.stub(method: :put) do |request|
|
330
|
+
Excon.stubs.pop
|
331
|
+
{status: 400, body: 'Bad inputs provided.'}
|
332
|
+
end
|
333
|
+
assert_raises Excon::Errors::BadRequest do
|
334
|
+
@client.close_app_ownership_event(@event_id, @user_hid, @app_hid,
|
335
|
+
@stop_time)
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'vault-usage-client/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = "vault-usage-client"
|
7
|
+
gem.version = Vault::Usage::Client::VERSION
|
8
|
+
gem.authors = ["Chris Continanza", "Jamu Kakar"]
|
9
|
+
gem.email = ["csquared@heroku.com", "jkakar@heroku.com"]
|
10
|
+
gem.description = "Client for Vault::Usage"
|
11
|
+
gem.summary = "A simple wrapper around the Vault::Usage HTTP API"
|
12
|
+
gem.homepage = "https://github.com/heroku/vault-usage-client"
|
13
|
+
|
14
|
+
gem.files = `git ls-files`.split($/)
|
15
|
+
gem.test_files = gem.files.grep('^(test|spec|features)/')
|
16
|
+
gem.require_paths = ["lib"]
|
17
|
+
|
18
|
+
gem.add_dependency 'excon'
|
19
|
+
gem.add_dependency 'yajl-ruby'
|
20
|
+
gem.add_dependency 'colorize'
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: vault-usage-client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Chris Continanza
|
9
|
+
- Jamu Kakar
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2013-02-21 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: excon
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ! '>='
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: '0'
|
31
|
+
- !ruby/object:Gem::Dependency
|
32
|
+
name: yajl-ruby
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
35
|
+
requirements:
|
36
|
+
- - ! '>='
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
type: :runtime
|
40
|
+
prerelease: false
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: colorize
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
description: Client for Vault::Usage
|
64
|
+
email:
|
65
|
+
- csquared@heroku.com
|
66
|
+
- jkakar@heroku.com
|
67
|
+
executables: []
|
68
|
+
extensions: []
|
69
|
+
extra_rdoc_files: []
|
70
|
+
files:
|
71
|
+
- .gitignore
|
72
|
+
- .yardopts
|
73
|
+
- Gemfile
|
74
|
+
- Gemfile.lock
|
75
|
+
- README.md
|
76
|
+
- Rakefile
|
77
|
+
- bin/vault-usage
|
78
|
+
- lib/vault-usage-client.rb
|
79
|
+
- lib/vault-usage-client/client.rb
|
80
|
+
- lib/vault-usage-client/version.rb
|
81
|
+
- test/client_test.rb
|
82
|
+
- test/helper.rb
|
83
|
+
- test/version_test.rb
|
84
|
+
- vault-usage-client.gemspec
|
85
|
+
homepage: https://github.com/heroku/vault-usage-client
|
86
|
+
licenses: []
|
87
|
+
post_install_message:
|
88
|
+
rdoc_options: []
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
none: false
|
93
|
+
requirements:
|
94
|
+
- - ! '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
segments:
|
98
|
+
- 0
|
99
|
+
hash: 3440025206376780543
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
|
+
none: false
|
102
|
+
requirements:
|
103
|
+
- - ! '>='
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
segments:
|
107
|
+
- 0
|
108
|
+
hash: 3440025206376780543
|
109
|
+
requirements: []
|
110
|
+
rubyforge_project:
|
111
|
+
rubygems_version: 1.8.23
|
112
|
+
signing_key:
|
113
|
+
specification_version: 3
|
114
|
+
summary: A simple wrapper around the Vault::Usage HTTP API
|
115
|
+
test_files: []
|
116
|
+
has_rdoc:
|