redfish_client 0.4.0 → 0.5.3
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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +21 -0
- data/.rubocop.yml +18 -0
- data/README.md +40 -0
- data/lib/redfish_client.rb +3 -6
- data/lib/redfish_client/connector.rb +172 -12
- data/lib/redfish_client/nil_hash.rb +44 -0
- data/lib/redfish_client/resource.rb +118 -34
- data/lib/redfish_client/response.rb +43 -0
- data/lib/redfish_client/root.rb +25 -70
- data/lib/redfish_client/version.rb +1 -1
- data/redfish_client.gemspec +3 -3
- metadata +23 -23
- data/.travis.yml +0 -32
- data/lib/redfish_client/caching_connector.rb +0 -38
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4a61fbe0cf26d1c84428c300e40f4c85dad097b85d1290ab7ea1f9515cf63198
|
4
|
+
data.tar.gz: 4b3d92086f62fdf2553b9eb635eaf8f0dad476b7030e4ea7c935961def7e191a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c6c9e428b744638d1e0ea9c508474ad109410c86098c662f6cd6640e299bbb647c48f2ceb113e9eabaa95968806b918d6edee879bb62c1f85e4641f9cd382c00
|
7
|
+
data.tar.gz: 79a3e67240e13830cdba2560b0b1bbf29fa027081ca3e3a0ba7896f9f66e3ddab29a53a98af431a533ee149c031faf11ce359adeeea1f82bdfcb86e1f01d9bbf
|
@@ -0,0 +1,21 @@
|
|
1
|
+
name: Ruby
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches: [ master ]
|
6
|
+
pull_request:
|
7
|
+
|
8
|
+
jobs:
|
9
|
+
test:
|
10
|
+
runs-on: ubuntu-latest
|
11
|
+
steps:
|
12
|
+
- name: Clone repo
|
13
|
+
uses: actions/checkout@v2
|
14
|
+
- name: Set up Ruby
|
15
|
+
uses: ruby/setup-ruby@v1
|
16
|
+
with:
|
17
|
+
ruby-version: 2.6
|
18
|
+
- name: Install dependencies
|
19
|
+
run: bundle install
|
20
|
+
- name: Run tests
|
21
|
+
run: bundle exec rake
|
data/.rubocop.yml
CHANGED
@@ -9,6 +9,9 @@ AllCops:
|
|
9
9
|
Layout/MultilineOperationIndentation:
|
10
10
|
EnforcedStyle: indented
|
11
11
|
|
12
|
+
Layout/MultilineMethodCallIndentation:
|
13
|
+
EnforcedStyle: indented
|
14
|
+
|
12
15
|
Style/MethodMissing:
|
13
16
|
Exclude:
|
14
17
|
- lib/redfish_client/resource.rb
|
@@ -22,9 +25,24 @@ Style/Documentation:
|
|
22
25
|
Style/BracesAroundHashParameters:
|
23
26
|
EnforcedStyle: context_dependent
|
24
27
|
|
28
|
+
Style/TrailingCommaInArguments:
|
29
|
+
EnforcedStyleForMultiline: comma
|
30
|
+
|
31
|
+
Style/TrailingCommaInArrayLiteral:
|
32
|
+
EnforcedStyleForMultiline: comma
|
33
|
+
|
34
|
+
Style/TrailingCommaInHashLiteral:
|
35
|
+
EnforcedStyleForMultiline: comma
|
36
|
+
|
25
37
|
Metrics/AbcSize:
|
26
38
|
Max: 20
|
27
39
|
|
28
40
|
Metrics/BlockLength:
|
29
41
|
Exclude:
|
30
42
|
- spec/**/*.rb
|
43
|
+
|
44
|
+
Metrics/ClassLength:
|
45
|
+
Max: 500
|
46
|
+
|
47
|
+
Metrics/ParameterLists:
|
48
|
+
CountKeywordArgs: false
|
data/README.md
CHANGED
@@ -41,6 +41,46 @@ Minimal program that uses this gem would look something like this:
|
|
41
41
|
root.logout
|
42
42
|
|
43
43
|
|
44
|
+
## Handling asynchronous operations
|
45
|
+
|
46
|
+
Redfish service can return a 202 status when we request an execution of a
|
47
|
+
long-running operation (e.g. updating firmware). We are expected to poll the
|
48
|
+
monitor for changes until the job terminates.
|
49
|
+
|
50
|
+
Responses in Redfish client have a built-in support for this, so polling the
|
51
|
+
service is rather painless:
|
52
|
+
|
53
|
+
# Start the async action
|
54
|
+
response = update_service.Actions["#UpdateService.SimpleUpdate"].post(
|
55
|
+
field: "target", payload: { ... },
|
56
|
+
)
|
57
|
+
# Wait for the termination
|
58
|
+
response = update_service.wait(response)
|
59
|
+
# Do something with response
|
60
|
+
|
61
|
+
It is also possible to manually poll the response like this:
|
62
|
+
|
63
|
+
response = update_service.Actions["#UpdateService.SimpleUpdate"].post(
|
64
|
+
field: "target", payload: { ... },
|
65
|
+
)
|
66
|
+
until response.done?
|
67
|
+
# wait a bit
|
68
|
+
response = update_service.get(response.monitor)
|
69
|
+
end
|
70
|
+
|
71
|
+
Response is also safe to (de)serialize, which means that the process that
|
72
|
+
started the async operation and the process that will wait for it can be
|
73
|
+
separate:
|
74
|
+
|
75
|
+
response = update_service.Actions["#UpdateService.SimpleUpdate"].post(
|
76
|
+
field: "target", payload: { ... },
|
77
|
+
)
|
78
|
+
send_response_somewhere(response.to_h)
|
79
|
+
|
80
|
+
# Somewhere else
|
81
|
+
response = Response.from_hash(receive_response_from_somewhere)
|
82
|
+
|
83
|
+
|
44
84
|
## Development
|
45
85
|
|
46
86
|
After checking out the repo, run `bin/setup` to install dependencies. Then,
|
data/lib/redfish_client.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "redfish_client/caching_connector"
|
4
3
|
require "redfish_client/connector"
|
4
|
+
require "redfish_client/nil_hash"
|
5
5
|
require "redfish_client/root"
|
6
6
|
require "redfish_client/version"
|
7
7
|
|
@@ -13,11 +13,8 @@ module RedfishClient
|
|
13
13
|
# @param verify [Boolean] verify certificates for https connections
|
14
14
|
# @param use_cache [Boolean] cache API responses
|
15
15
|
def self.new(url, prefix: "/redfish/v1", verify: true, use_cache: true)
|
16
|
-
|
17
|
-
|
18
|
-
else
|
19
|
-
Connector.new(url, verify)
|
20
|
-
end
|
16
|
+
cache = (use_cache ? Hash : NilHash).new
|
17
|
+
con = Connector.new(url, verify: verify, cache: cache)
|
21
18
|
Root.new(con, oid: prefix)
|
22
19
|
end
|
23
20
|
end
|
@@ -1,8 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "base64"
|
3
4
|
require "excon"
|
4
5
|
require "json"
|
5
6
|
|
7
|
+
require "redfish_client/nil_hash"
|
8
|
+
require "redfish_client/response"
|
9
|
+
|
6
10
|
module RedfishClient
|
7
11
|
# Connector serves as a low-level wrapper around HTTP calls that are used
|
8
12
|
# to retrieve data from the service API. It abstracts away implementation
|
@@ -12,18 +16,39 @@ module RedfishClient
|
|
12
16
|
# Library users should treat this class as an implementation detail and
|
13
17
|
# use higer-level {RedfishClient::Resource} instead.
|
14
18
|
class Connector
|
19
|
+
# AuthError is raised if the credentials are invalid.
|
20
|
+
class AuthError < StandardError; end
|
21
|
+
|
15
22
|
# Default headers, as required by Redfish spec
|
16
23
|
# https://redfish.dmtf.org/schemas/DSP0266_1.4.0.html#request-headers
|
17
24
|
DEFAULT_HEADERS = {
|
18
25
|
"Accept" => "application/json",
|
19
|
-
"OData-Version" => "4.0"
|
26
|
+
"OData-Version" => "4.0",
|
20
27
|
}.freeze
|
21
28
|
|
29
|
+
# Basic and token authentication header names
|
30
|
+
BASIC_AUTH_HEADER = "Authorization"
|
31
|
+
TOKEN_AUTH_HEADER = "X-Auth-Token"
|
32
|
+
LOCATION_HEADER = "Location"
|
33
|
+
|
22
34
|
# Create new connector.
|
23
35
|
#
|
36
|
+
# By default, connector performs no caching. If caching is desired,
|
37
|
+
# Hash should be used as a cache implementation.
|
38
|
+
#
|
39
|
+
# It is also possible to pass in custom caching class. Instances of that
|
40
|
+
# class should respond to the following four methods:
|
41
|
+
#
|
42
|
+
# 1. `[](key)` - Used to access cached content and should return
|
43
|
+
# `nil` if the key has no associated value.
|
44
|
+
# 2. `[]=(key, value)` - Cache `value` under the `key`
|
45
|
+
# 3. `clear` - Clear the complete cache.
|
46
|
+
# 4. `delete(key)` - Invalidate cache entry associated with `key`.
|
47
|
+
#
|
24
48
|
# @param url [String] base url of the Redfish service
|
25
49
|
# @param verify [Boolean] verify SSL certificate of the service
|
26
|
-
|
50
|
+
# @param cache [Object] cache backend
|
51
|
+
def initialize(url, verify: true, cache: nil)
|
27
52
|
@url = url
|
28
53
|
@headers = DEFAULT_HEADERS.dup
|
29
54
|
middlewares = Excon.defaults[:middlewares] +
|
@@ -31,6 +56,7 @@ module RedfishClient
|
|
31
56
|
@connection = Excon.new(@url,
|
32
57
|
ssl_verify_peer: verify,
|
33
58
|
middlewares: middlewares)
|
59
|
+
@cache = cache || NilHash.new
|
34
60
|
end
|
35
61
|
|
36
62
|
# Add HTTP headers to the requests made by the connector.
|
@@ -50,44 +76,131 @@ module RedfishClient
|
|
50
76
|
headers.each { |h| @headers.delete(h) }
|
51
77
|
end
|
52
78
|
|
79
|
+
# Issue requests to the service.
|
80
|
+
#
|
81
|
+
# @param mathod [Symbol] HTTP method (:get, :post, :patch or :delete)
|
82
|
+
# @param path [String] path to the resource, relative to the base
|
83
|
+
# @param data [Hash] data to be sent over the socket
|
84
|
+
# @return [Response] response object
|
85
|
+
def request(method, path, data = nil)
|
86
|
+
return @cache[path] if method == :get && @cache[path]
|
87
|
+
|
88
|
+
do_request(method, path, data).tap do |r|
|
89
|
+
@cache[path] = r if method == :get && r.status == 200
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
53
93
|
# Issue GET request to service.
|
54
94
|
#
|
95
|
+
# This method will first try to return cached response if available. If
|
96
|
+
# cache does not contain entry for this request, data will be fetched from
|
97
|
+
# remote and then cached, but only if the response has an OK (200) status.
|
98
|
+
#
|
55
99
|
# @param path [String] path to the resource, relative to the base url
|
56
|
-
# @return [
|
100
|
+
# @return [Response] response object
|
57
101
|
def get(path)
|
58
|
-
|
102
|
+
request(:get, path)
|
59
103
|
end
|
60
104
|
|
61
105
|
# Issue POST requests to the service.
|
62
106
|
#
|
63
107
|
# @param path [String] path to the resource, relative to the base
|
64
108
|
# @param data [Hash] data to be sent over the socket, JSON encoded
|
65
|
-
# @return [
|
109
|
+
# @return [Response] response object
|
66
110
|
def post(path, data = nil)
|
67
|
-
|
111
|
+
request(:post, path, data)
|
68
112
|
end
|
69
113
|
|
70
114
|
# Issue PATCH requests to the service.
|
71
115
|
#
|
72
116
|
# @param path [String] path to the resource, relative to the base
|
73
117
|
# @param data [Hash] data to be sent over the socket
|
74
|
-
# @return [
|
118
|
+
# @return [Response] response object
|
75
119
|
def patch(path, data = nil)
|
76
|
-
|
120
|
+
request(:patch, path, data)
|
77
121
|
end
|
78
122
|
|
79
123
|
# Issue DELETE requests to the service.
|
80
124
|
#
|
81
125
|
# @param path [String] path to the resource, relative to the base
|
82
|
-
# @return [
|
126
|
+
# @return [Response] response object
|
83
127
|
def delete(path)
|
84
|
-
|
128
|
+
request(:delete, path)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Clear the cached responses.
|
132
|
+
#
|
133
|
+
# If path is passed as a parameter, only one cache entry gets invalidated,
|
134
|
+
# else complete cache gets invalidated.
|
135
|
+
#
|
136
|
+
# Next GET request will repopulate the cache.
|
137
|
+
#
|
138
|
+
# @param path [String] path to invalidate
|
139
|
+
def reset(path = nil)
|
140
|
+
path.nil? ? @cache.clear : @cache.delete(path)
|
141
|
+
end
|
142
|
+
|
143
|
+
# Set authentication-related variables.
|
144
|
+
#
|
145
|
+
# Last parameter controls the kind of login connector will perform. If
|
146
|
+
# session_path is `nil`, basic authentication will be used, otherwise
|
147
|
+
# connector will use session-based authentication.
|
148
|
+
#
|
149
|
+
# Note that actual login is done lazily. If you need to check for
|
150
|
+
# credential validity, call #{login} method.
|
151
|
+
#
|
152
|
+
# @param username [String] API username
|
153
|
+
# @param password [String] API password
|
154
|
+
# @param auth_test_path [String] API path to test credential's validity
|
155
|
+
# @param session_path [String, nil] API session path
|
156
|
+
def set_auth_info(username, password, auth_test_path, session_path = nil)
|
157
|
+
@username = username
|
158
|
+
@password = password
|
159
|
+
@auth_test_path = auth_test_path
|
160
|
+
@session_path = session_path
|
161
|
+
end
|
162
|
+
|
163
|
+
# Authenticate against the service.
|
164
|
+
#
|
165
|
+
# Calling this method will try to authenticate against API using
|
166
|
+
# credentials provided by #{set_auth_info} call.
|
167
|
+
# If authentication fails, # {AuthError} will be raised.
|
168
|
+
#
|
169
|
+
# @raise [AuthError] if credentials are invalid
|
170
|
+
def login
|
171
|
+
@session_path ? session_login : basic_login
|
172
|
+
end
|
173
|
+
|
174
|
+
# Sign out of the service.
|
175
|
+
def logout
|
176
|
+
# We bypass request here because we do not want any retries on 401
|
177
|
+
# when doing logout.
|
178
|
+
if @session_oid
|
179
|
+
params = prepare_request_params(:delete, @session_oid)
|
180
|
+
@connection.request(params)
|
181
|
+
@session_oid = nil
|
182
|
+
end
|
183
|
+
remove_headers([BASIC_AUTH_HEADER, TOKEN_AUTH_HEADER])
|
85
184
|
end
|
86
185
|
|
87
186
|
private
|
88
187
|
|
89
|
-
def
|
90
|
-
params =
|
188
|
+
def do_request(method, path, data)
|
189
|
+
params = prepare_request_params(method, path, data)
|
190
|
+
r = @connection.request(params)
|
191
|
+
if r.status == 401
|
192
|
+
login
|
193
|
+
r = @connection.request(params)
|
194
|
+
end
|
195
|
+
Response.new(r.status, downcase_headers(r.data[:headers]), r.data[:body])
|
196
|
+
end
|
197
|
+
|
198
|
+
def downcase_headers(headers)
|
199
|
+
headers.each_with_object({}) { |(k, v), obj| obj[k.downcase] = v }
|
200
|
+
end
|
201
|
+
|
202
|
+
def prepare_request_params(method, path, data = nil)
|
203
|
+
params = { method: method, path: path }
|
91
204
|
if data
|
92
205
|
params[:body] = data.to_json
|
93
206
|
params[:headers] = @headers.merge("Content-Type" => "application/json")
|
@@ -96,5 +209,52 @@ module RedfishClient
|
|
96
209
|
end
|
97
210
|
params
|
98
211
|
end
|
212
|
+
|
213
|
+
def session_login
|
214
|
+
# We bypass request here because we do not want any retries on 401
|
215
|
+
# when doing login.
|
216
|
+
params = prepare_request_params(:post, @session_path,
|
217
|
+
"UserName" => @username,
|
218
|
+
"Password" => @password)
|
219
|
+
r = @connection.request(params)
|
220
|
+
raise_invalid_auth_error unless r.status == 201
|
221
|
+
|
222
|
+
body = JSON.parse(r.data[:body])
|
223
|
+
headers = r.data[:headers]
|
224
|
+
|
225
|
+
add_headers(TOKEN_AUTH_HEADER => headers[TOKEN_AUTH_HEADER])
|
226
|
+
save_session_oid!(body, headers)
|
227
|
+
end
|
228
|
+
|
229
|
+
def save_session_oid!(body, headers)
|
230
|
+
@session_oid = body["@odata.id"] if body.key?("@odata.id")
|
231
|
+
return if @session_oid
|
232
|
+
|
233
|
+
return unless headers.key?(LOCATION_HEADER)
|
234
|
+
|
235
|
+
location = URI.parse(headers[LOCATION_HEADER])
|
236
|
+
@session_oid = [location.path, location.query].compact.join("?")
|
237
|
+
end
|
238
|
+
|
239
|
+
def basic_login
|
240
|
+
payload = Base64.encode64("#{@username}:#{@password}").strip
|
241
|
+
add_headers(BASIC_AUTH_HEADER => "Basic #{payload}")
|
242
|
+
return if auth_valid?
|
243
|
+
|
244
|
+
remove_headers([BASIC_AUTH_HEADER])
|
245
|
+
raise_invalid_auth_error
|
246
|
+
end
|
247
|
+
|
248
|
+
def raise_invalid_auth_error
|
249
|
+
raise AuthError, "Invalid credentials"
|
250
|
+
end
|
251
|
+
|
252
|
+
def auth_valid?
|
253
|
+
# We bypass request here because we do not want any retries on 401
|
254
|
+
# when checking authentication headers.
|
255
|
+
reset(@auth_test_path) # Do not want to see cached response
|
256
|
+
params = prepare_request_params(:get, @auth_test_path)
|
257
|
+
@connection.request(params).status == 200
|
258
|
+
end
|
99
259
|
end
|
100
260
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RedfishClient
|
4
|
+
# NilHash imitates the built-in Hash class without storing anything
|
5
|
+
# permanently.
|
6
|
+
#
|
7
|
+
# Main use of this class is as a non-caching connector backend.
|
8
|
+
class NilHash
|
9
|
+
# Access hash member.
|
10
|
+
#
|
11
|
+
# Since this implementation does not store any data, return value is
|
12
|
+
# always nil.
|
13
|
+
#
|
14
|
+
# @param _key not used
|
15
|
+
# @return [nil]
|
16
|
+
def [](_key)
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
# Set hash member.
|
21
|
+
#
|
22
|
+
# This is just a pass-through method, since it always simply returns the
|
23
|
+
# value without actually storing it.
|
24
|
+
#
|
25
|
+
# @param _key not used
|
26
|
+
# @param value [Object] any value
|
27
|
+
# @return [Object] value
|
28
|
+
def []=(_key, value)
|
29
|
+
value
|
30
|
+
end
|
31
|
+
|
32
|
+
# Clear the contents of the cache.
|
33
|
+
#
|
34
|
+
# Since hash is not storing anything, this is a no-op.
|
35
|
+
def clear; end
|
36
|
+
|
37
|
+
# Delete entry from hash.
|
38
|
+
#
|
39
|
+
# Since hash is not storing anything, this is a no-op.
|
40
|
+
#
|
41
|
+
# @param _key not used
|
42
|
+
def delete(_key) end
|
43
|
+
end
|
44
|
+
end
|
@@ -17,8 +17,8 @@ module RedfishClient
|
|
17
17
|
#
|
18
18
|
# In order to reduce the amount of requests being sent to the service,
|
19
19
|
# resource can also utilise caching connector. If we would like to get
|
20
|
-
# fresh values from the service, {#
|
21
|
-
#
|
20
|
+
# fresh values from the service, {#refresh} call will flush the cache and
|
21
|
+
# retrieve fresh data from the remote.
|
22
22
|
class Resource
|
23
23
|
# NoODataId error is raised when operation would need OpenData id of the
|
24
24
|
# resource to accomplish the task a hand.
|
@@ -28,9 +28,20 @@ module RedfishClient
|
|
28
28
|
# resource.
|
29
29
|
class NoResource < StandardError; end
|
30
30
|
|
31
|
+
# Timeout error is raised if the async request is not handled in due time.
|
32
|
+
class Timeout < StandardError; end
|
33
|
+
|
31
34
|
# Headers, returned from the service when resource has been constructed.
|
35
|
+
#
|
36
|
+
# @return [Hash] resource headers
|
32
37
|
attr_reader :headers
|
33
38
|
|
39
|
+
# Raw data that has been used to construct resource by either fetching it
|
40
|
+
# from the remote API or by being passed-in as a parameter to constructor.
|
41
|
+
#
|
42
|
+
# @return [Hash] resource raw data
|
43
|
+
attr_reader :raw
|
44
|
+
|
34
45
|
# Create new resource.
|
35
46
|
#
|
36
47
|
# Resource can be created either by passing in OpenData identifier or
|
@@ -41,16 +52,36 @@ module RedfishClient
|
|
41
52
|
# @param connector [RedfishClient::Connector] connector that will be used
|
42
53
|
# to fetch the resources
|
43
54
|
# @param oid [String] OpenData id of the resource
|
44
|
-
# @param
|
55
|
+
# @param raw [Hash] raw content to populate resource with
|
45
56
|
# @raise [NoResource] resource cannot be retrieved from the service
|
46
|
-
def initialize(connector, oid: nil,
|
57
|
+
def initialize(connector, oid: nil, raw: nil)
|
47
58
|
@connector = connector
|
48
|
-
|
49
59
|
if oid
|
50
60
|
initialize_from_service(oid)
|
51
61
|
else
|
52
|
-
@
|
62
|
+
@raw = raw
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Wait for the potentially async operation to terminate
|
67
|
+
#
|
68
|
+
# Note that this can be safely called on response from non-async
|
69
|
+
# operations where the function will return immediately and without making
|
70
|
+
# any additional requests to the service.
|
71
|
+
#
|
72
|
+
# @param response [RedfishClient::Response] response
|
73
|
+
# @param retries [Integer] number of retries
|
74
|
+
# @param delay [Integer] number of seconds between retries
|
75
|
+
# @return [RedfishClient::Response] final response
|
76
|
+
# @raise [Timeout] if the operation did not terminate in time
|
77
|
+
def wait(response, retries: 10, delay: 1)
|
78
|
+
retries.times do |_i|
|
79
|
+
return response if response.done?
|
80
|
+
|
81
|
+
sleep(delay)
|
82
|
+
response = get(path: response.monitor)
|
53
83
|
end
|
84
|
+
raise Timeout, "Async operation did not terminate in allotted time"
|
54
85
|
end
|
55
86
|
|
56
87
|
# Access resource content.
|
@@ -61,7 +92,7 @@ module RedfishClient
|
|
61
92
|
# @param attr [String] key for accessing data
|
62
93
|
# @return associated value or `nil` if attr is missing
|
63
94
|
def [](attr)
|
64
|
-
build_resource(
|
95
|
+
build_resource(raw[attr])
|
65
96
|
end
|
66
97
|
|
67
98
|
# Safely access nested resource content.
|
@@ -82,7 +113,7 @@ module RedfishClient
|
|
82
113
|
# @param name [String, Symbol] key name to test
|
83
114
|
# @return [Boolean] inclusion test result
|
84
115
|
def key?(name)
|
85
|
-
|
116
|
+
raw.key?(name.to_s)
|
86
117
|
end
|
87
118
|
|
88
119
|
# Convenience access for resource data.
|
@@ -96,25 +127,52 @@ module RedfishClient
|
|
96
127
|
key?(symbol.to_s) || super
|
97
128
|
end
|
98
129
|
|
99
|
-
#
|
130
|
+
# Pretty-print the wrapped content.
|
100
131
|
#
|
101
|
-
#
|
102
|
-
def
|
103
|
-
|
132
|
+
# @return [String] JSON-serialized raw data
|
133
|
+
def to_s
|
134
|
+
JSON.pretty_generate(raw)
|
104
135
|
end
|
105
136
|
|
106
|
-
#
|
137
|
+
# Issue a requests to the selected endpoint.
|
138
|
+
#
|
139
|
+
# By default, request will be sent to the path, stored in `@odata.id`
|
140
|
+
# field. Source field can be changed by specifying the `field` parameter
|
141
|
+
# when calling this function. Specifying the `path` argument will bypass
|
142
|
+
# the field lookup altogether and issue a request directly to the selected
|
143
|
+
# path.
|
107
144
|
#
|
108
|
-
#
|
109
|
-
|
110
|
-
|
145
|
+
# If the resource has no lookup field, {NoODataId} error will be raised,
|
146
|
+
# since posting to non-networked resources makes no sense and probably
|
147
|
+
# indicates bug in library consumer.
|
148
|
+
#
|
149
|
+
# @param method [Symbol] HTTP method (:get, :post, :patch or :delete)
|
150
|
+
# @param field [String, Symbol] path lookup field
|
151
|
+
# @param path [String] path to post to
|
152
|
+
# @return [RedfishClient::Response] response
|
153
|
+
# @raise [NoODataId] resource has no OpenData id
|
154
|
+
def request(method, field, path, payload = nil)
|
155
|
+
@connector.request(method, get_path(field, path), payload)
|
111
156
|
end
|
112
157
|
|
113
|
-
#
|
158
|
+
# Issue a GET requests to the selected endpoint.
|
114
159
|
#
|
115
|
-
#
|
116
|
-
|
117
|
-
|
160
|
+
# By default, GET request will be sent to the path, stored in `@odata.id`
|
161
|
+
# field. Source field can be changed by specifying the `field` parameter
|
162
|
+
# when calling this function. Specifying the `path` argument will bypass
|
163
|
+
# the field lookup altogether and issue a GET request directly to the
|
164
|
+
# selected path.
|
165
|
+
#
|
166
|
+
# If the resource has no lookup field, {NoODataId} error will be raised,
|
167
|
+
# since posting to non-networked resources makes no sense and probably
|
168
|
+
# indicates bug in library consumer.
|
169
|
+
#
|
170
|
+
# @param field [String, Symbol] path lookup field
|
171
|
+
# @param path [String] path to post to
|
172
|
+
# @return [RedfishClient::Response] response
|
173
|
+
# @raise [NoODataId] resource has no OpenData id
|
174
|
+
def get(field: "@odata.id", path: nil)
|
175
|
+
request(:get, field, path)
|
118
176
|
end
|
119
177
|
|
120
178
|
# Issue a POST requests to the selected endpoint.
|
@@ -135,10 +193,10 @@ module RedfishClient
|
|
135
193
|
# @param field [String, Symbol] path lookup field
|
136
194
|
# @param path [String] path to post to
|
137
195
|
# @param payload [Hash<String, >] data to send
|
138
|
-
# @return [
|
196
|
+
# @return [RedfishClient::Response] response
|
139
197
|
# @raise [NoODataId] resource has no OpenData id
|
140
198
|
def post(field: "@odata.id", path: nil, payload: nil)
|
141
|
-
|
199
|
+
request(:post, field, path, payload)
|
142
200
|
end
|
143
201
|
|
144
202
|
# Issue a PATCH requests to the selected endpoint.
|
@@ -149,10 +207,10 @@ module RedfishClient
|
|
149
207
|
# @param field [String, Symbol] path lookup field
|
150
208
|
# @param path [String] path to patch
|
151
209
|
# @param payload [Hash<String, >] data to send
|
152
|
-
# @return [
|
210
|
+
# @return [RedfishClient::Response] response
|
153
211
|
# @raise [NoODataId] resource has no OpenData id
|
154
212
|
def patch(field: "@odata.id", path: nil, payload: nil)
|
155
|
-
|
213
|
+
request(:patch, field, path, payload)
|
156
214
|
end
|
157
215
|
|
158
216
|
# Issue a DELETE requests to the endpoint of the resource.
|
@@ -161,26 +219,52 @@ module RedfishClient
|
|
161
219
|
# raised, since deleting non-networked resources makes no sense and
|
162
220
|
# probably indicates bug in library consumer.
|
163
221
|
#
|
164
|
-
# @return [
|
222
|
+
# @return [RedfishClient::Response] response
|
165
223
|
# @raise [NoODataId] resource has no OpenData id
|
166
|
-
def delete
|
167
|
-
|
224
|
+
def delete(field: "@odata.id", path: nil, payload: nil)
|
225
|
+
request(:delete, field, path, payload)
|
226
|
+
end
|
227
|
+
|
228
|
+
# Refresh resource content from the API
|
229
|
+
#
|
230
|
+
# Caling this method will ensure that the resource data is in sync with
|
231
|
+
# the Redfis API, invalidating any caches as necessary.
|
232
|
+
def refresh
|
233
|
+
return unless self["@odata.id"]
|
234
|
+
|
235
|
+
# TODO(@tadeboro): raise more sensible exception if resource cannot be
|
236
|
+
# refreshed.
|
237
|
+
@connector.reset(self["@odata.id"])
|
238
|
+
initialize_from_service(self["@odata.id"])
|
168
239
|
end
|
169
240
|
|
170
241
|
private
|
171
242
|
|
172
243
|
def initialize_from_service(oid)
|
173
|
-
|
174
|
-
|
244
|
+
url, fragment = oid.split("#", 2)
|
245
|
+
resp = wait(get(path: url))
|
246
|
+
raise NoResource unless [200, 201].include?(resp.status)
|
247
|
+
|
248
|
+
@raw = get_fragment(JSON.parse(resp.body), fragment)
|
249
|
+
@raw["@odata.id"] = oid
|
250
|
+
@headers = resp.headers
|
251
|
+
end
|
252
|
+
|
253
|
+
def get_fragment(data, fragment)
|
254
|
+
# data, /my/0/part -> data["my"][0]["part"]
|
255
|
+
parse_fragment_string(fragment).reduce(data) do |acc, c|
|
256
|
+
acc[acc.is_a?(Array) ? c.to_i : c]
|
257
|
+
end
|
258
|
+
end
|
175
259
|
|
176
|
-
|
177
|
-
|
178
|
-
|
260
|
+
def parse_fragment_string(fragment)
|
261
|
+
# /my/0/part -> ["my", "0", "part"]
|
262
|
+
fragment ? fragment.split("/").reject { |i| i == "" } : []
|
179
263
|
end
|
180
264
|
|
181
265
|
def get_path(field, path)
|
182
266
|
raise NoODataId if path.nil? && !key?(field)
|
183
|
-
path ||
|
267
|
+
path || raw[field]
|
184
268
|
end
|
185
269
|
|
186
270
|
def build_resource(data)
|
@@ -197,7 +281,7 @@ module RedfishClient
|
|
197
281
|
if data.key?("@odata.id")
|
198
282
|
Resource.new(@connector, oid: data["@odata.id"])
|
199
283
|
else
|
200
|
-
Resource.new(@connector,
|
284
|
+
Resource.new(@connector, raw: data)
|
201
285
|
end
|
202
286
|
rescue NoResource
|
203
287
|
nil
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "uri"
|
4
|
+
|
5
|
+
module RedfishClient
|
6
|
+
# Response struct.
|
7
|
+
#
|
8
|
+
# This struct is returned from the methods that interact with the remote API.
|
9
|
+
class Response
|
10
|
+
attr_reader :status
|
11
|
+
attr_reader :headers
|
12
|
+
attr_reader :body
|
13
|
+
|
14
|
+
def initialize(status, headers, body)
|
15
|
+
@status = status
|
16
|
+
@headers = headers
|
17
|
+
@body = body
|
18
|
+
end
|
19
|
+
|
20
|
+
def done?
|
21
|
+
status != 202
|
22
|
+
end
|
23
|
+
|
24
|
+
def monitor
|
25
|
+
return nil if done?
|
26
|
+
|
27
|
+
uri = URI.parse(headers["location"])
|
28
|
+
[uri.path, uri.query].compact.join("?")
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_h
|
32
|
+
{ "status" => status, "headers" => headers, "body" => body }
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_s
|
36
|
+
"Response[status=#{status}, headers=#{headers}, body='#{body}']"
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.from_hash(data)
|
40
|
+
new(*data.values_at("status", "headers", "body"))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/redfish_client/root.rb
CHANGED
@@ -1,50 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "base64"
|
4
|
-
require "json"
|
5
3
|
require "server_sent_events"
|
6
4
|
|
5
|
+
require "redfish_client/event_listener"
|
7
6
|
require "redfish_client/resource"
|
8
7
|
|
9
8
|
module RedfishClient
|
10
9
|
# Root resource represents toplevel entry point into Redfish service data.
|
11
10
|
# Its main purpose is to provide authentication support for the API.
|
12
11
|
class Root < Resource
|
13
|
-
# AuthError is raised if the user session cannot be created.
|
14
|
-
class AuthError < StandardError; end
|
15
|
-
|
16
|
-
# Basic and token authentication headers.
|
17
|
-
BASIC_AUTH_HEADER = "Authorization"
|
18
|
-
TOKEN_AUTH_HEADER = "X-Auth-Token"
|
19
|
-
|
20
|
-
# Authenticate against the service.
|
21
|
-
#
|
22
|
-
# Calling this method will try to create new session on the service using
|
23
|
-
# provided credentials. If the session creation fails, basic
|
24
|
-
# authentication will be attempted. If basic authentication fails,
|
25
|
-
# {AuthError} will be raised.
|
26
|
-
#
|
27
|
-
# @param username [String] username
|
28
|
-
# @param password [String] password
|
29
|
-
# @raise [AuthError] if user session could not be created
|
30
|
-
def login(username, password)
|
31
|
-
# Since session auth is more secure, we try it first and use basic auth
|
32
|
-
# only if session auth is not available.
|
33
|
-
if session_login_available?
|
34
|
-
session_login(username, password)
|
35
|
-
else
|
36
|
-
basic_login(username, password)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
# Sign out of the service.
|
41
|
-
#
|
42
|
-
# If the session could not be deleted, {AuthError} will be raised.
|
43
|
-
def logout
|
44
|
-
session_logout
|
45
|
-
basic_logout
|
46
|
-
end
|
47
|
-
|
48
12
|
# Find Redfish service object by OData ID field.
|
49
13
|
#
|
50
14
|
# @param oid [String] Odata id of the resource
|
@@ -76,47 +40,38 @@ module RedfishClient
|
|
76
40
|
EventListener.new(ServerSentEvents.create_client(address))
|
77
41
|
end
|
78
42
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
43
|
+
# Authenticate against the service.
|
44
|
+
#
|
45
|
+
# Calling this method will select the appropriate method of authentication
|
46
|
+
# and try to login using provided credentials.
|
47
|
+
#
|
48
|
+
# @param username [String] username
|
49
|
+
# @param password [String] password
|
50
|
+
# @raise [RedfishClient::AuthenticatedConnector::AuthError] if user
|
51
|
+
# session could not be authenticated
|
52
|
+
def login(username, password)
|
53
|
+
@connector.set_auth_info(
|
54
|
+
username, password, auth_test_path, session_path
|
89
55
|
)
|
90
|
-
|
91
|
-
|
92
|
-
session_logout
|
93
|
-
|
94
|
-
payload = r.data[:headers][TOKEN_AUTH_HEADER]
|
95
|
-
@connector.add_headers(TOKEN_AUTH_HEADER => payload)
|
96
|
-
@session = Resource.new(@connector, content: JSON.parse(r.data[:body]))
|
56
|
+
@connector.login
|
97
57
|
end
|
98
58
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
raise AuthError unless r.status == 204
|
103
|
-
@session = nil
|
104
|
-
@connector.remove_headers([TOKEN_AUTH_HEADER])
|
59
|
+
# Sign out of the service.
|
60
|
+
def logout
|
61
|
+
@connector.logout
|
105
62
|
end
|
106
63
|
|
107
|
-
|
108
|
-
@content.values.map { |v| v["@odata.id"] }.compact.first
|
109
|
-
end
|
64
|
+
private
|
110
65
|
|
111
|
-
def
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
66
|
+
def session_path
|
67
|
+
# We access raw values here on purpose, since calling dig on resource
|
68
|
+
# instance would try to download the sessions collection, which would
|
69
|
+
# fail since we are not yet logged in.
|
70
|
+
raw.dig("Links", "Sessions", "@odata.id")
|
116
71
|
end
|
117
72
|
|
118
|
-
def
|
119
|
-
@
|
73
|
+
def auth_test_path
|
74
|
+
raw.values.find { |v| v["@odata.id"] }["@odata.id"]
|
120
75
|
end
|
121
76
|
end
|
122
77
|
end
|
data/redfish_client.gemspec
CHANGED
@@ -27,15 +27,15 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
28
|
spec.require_paths = ["lib"]
|
29
29
|
|
30
|
-
spec.required_ruby_version = "
|
30
|
+
spec.required_ruby_version = ">= 2.1"
|
31
31
|
|
32
|
-
spec.add_runtime_dependency "excon", "~> 0.
|
32
|
+
spec.add_runtime_dependency "excon", "~> 0.71"
|
33
33
|
spec.add_runtime_dependency "server_sent_events", "~> 0.1"
|
34
34
|
|
35
|
-
spec.add_development_dependency "bundler", "~> 1.16"
|
36
35
|
spec.add_development_dependency "rake", ">= 11.0"
|
37
36
|
spec.add_development_dependency "rspec", ">= 3.7"
|
38
37
|
spec.add_development_dependency "simplecov"
|
38
|
+
spec.add_development_dependency "webmock", "~> 3.4"
|
39
39
|
spec.add_development_dependency "yard"
|
40
40
|
spec.add_development_dependency "rubocop", "~> 0.54.0"
|
41
41
|
spec.add_development_dependency "pry"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redfish_client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tadej Borovšak
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-03-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: excon
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '0.
|
19
|
+
version: '0.71'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '0.
|
26
|
+
version: '0.71'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: server_sent_events
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -38,20 +38,6 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0.1'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: bundler
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '1.16'
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '1.16'
|
55
41
|
- !ruby/object:Gem::Dependency
|
56
42
|
name: rake
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -94,6 +80,20 @@ dependencies:
|
|
94
80
|
- - ">="
|
95
81
|
- !ruby/object:Gem::Version
|
96
82
|
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: webmock
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.4'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3.4'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: yard
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -144,11 +144,11 @@ extensions: []
|
|
144
144
|
extra_rdoc_files: []
|
145
145
|
files:
|
146
146
|
- ".codeclimate.yml"
|
147
|
+
- ".github/workflows/ci.yml"
|
147
148
|
- ".gitignore"
|
148
149
|
- ".rspec"
|
149
150
|
- ".rubocop.yml"
|
150
151
|
- ".simplecov"
|
151
|
-
- ".travis.yml"
|
152
152
|
- ".yardopts"
|
153
153
|
- Gemfile
|
154
154
|
- README.md
|
@@ -156,10 +156,11 @@ files:
|
|
156
156
|
- bin/console
|
157
157
|
- bin/setup
|
158
158
|
- lib/redfish_client.rb
|
159
|
-
- lib/redfish_client/caching_connector.rb
|
160
159
|
- lib/redfish_client/connector.rb
|
161
160
|
- lib/redfish_client/event_listener.rb
|
161
|
+
- lib/redfish_client/nil_hash.rb
|
162
162
|
- lib/redfish_client/resource.rb
|
163
|
+
- lib/redfish_client/response.rb
|
163
164
|
- lib/redfish_client/root.rb
|
164
165
|
- lib/redfish_client/version.rb
|
165
166
|
- redfish_client.gemspec
|
@@ -174,7 +175,7 @@ require_paths:
|
|
174
175
|
- lib
|
175
176
|
required_ruby_version: !ruby/object:Gem::Requirement
|
176
177
|
requirements:
|
177
|
-
- - "
|
178
|
+
- - ">="
|
178
179
|
- !ruby/object:Gem::Version
|
179
180
|
version: '2.1'
|
180
181
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
@@ -183,8 +184,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
183
184
|
- !ruby/object:Gem::Version
|
184
185
|
version: '0'
|
185
186
|
requirements: []
|
186
|
-
|
187
|
-
rubygems_version: 2.7.7
|
187
|
+
rubygems_version: 3.2.3
|
188
188
|
signing_key:
|
189
189
|
specification_version: 4
|
190
190
|
summary: Simple Redfish client library
|
data/.travis.yml
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
sudo: false
|
2
|
-
language: ruby
|
3
|
-
cache: bundler
|
4
|
-
|
5
|
-
rvm:
|
6
|
-
- 2.4.3
|
7
|
-
|
8
|
-
env:
|
9
|
-
global:
|
10
|
-
- CC_TEST_REPORTER_ID=64aae57d1a096aebb16347cbc64d418352f3ec38ebbe4d002bc84c2ceda837b3
|
11
|
-
|
12
|
-
before_script:
|
13
|
-
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
14
|
-
- chmod +x ./cc-test-reporter
|
15
|
-
- ./cc-test-reporter before-build
|
16
|
-
|
17
|
-
after_script:
|
18
|
-
- ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
|
19
|
-
|
20
|
-
branches:
|
21
|
-
only:
|
22
|
-
- master
|
23
|
-
- /^\d+\.\d+(\.\d+)?(-\S*)?$/
|
24
|
-
|
25
|
-
deploy:
|
26
|
-
provider: rubygems
|
27
|
-
api_key:
|
28
|
-
secure: NdOd7EA9XQZNbnkzLFNXeJcOoaTi93uuwzjADS+fxFJ2HYGJs/T+gi98f742Oj4j187lBQjwICkdduGQjVn3BA/8YZD7sOkdywDSELupTjJmugf/hks6MiKJ0fex5GmHwspjZ7P+9iLPXGcaYWbGamWlSDL+Z/79s4bi8b5sBVwJ01YnIJvFn32/I5vPFwbwUzvH0n3HAjYHZ/kpZ122Zp/DhSpGam2azWF0Tynu7RNMsQrGamAH0mC1DzAscSD6VTyMcSMkD6XUr3GX7w7yvVO0qo7e6EcjrsJKMRC8/s2C/SWQ4pRDAldhQpd4C7/QSaHE0mmQcEYi2qsBmrbFb7cAfRmS5BKn8LSv7YPVH0VFl3fwYAycwqA068TZ68t99YPw7uVcJpDOWBODCQWA1wgV1tXHop7CJOcQW4MoXzyaRbbpF+6lyxXEgTz8Me8zEucMsHWzS9dI1E5x/bWl3TgEM9n+/G6BAcBj/mZv/dxC6H7kWp0ubKw0bRsdET/YmG53vxISaFbnLxNhsHzeE2mFeF+JL7a1amlC4aTiSBnxDgRzDdnoo42Hu8SUg/mp0xLjyoTME1w1zaB+GvmWCdrpkLQL2kNDgQUvBfOp+Zqfb37tKgc15zb7v2sru5acixkSG9UVElleDW8K4R1QcqXwLxCYccCi/ExVR1zSvSM=
|
29
|
-
gem: redfish_client
|
30
|
-
on:
|
31
|
-
tags: true
|
32
|
-
repo: xlab-si/redfish-client-ruby
|
@@ -1,38 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "redfish_client/connector"
|
4
|
-
|
5
|
-
module RedfishClient
|
6
|
-
# Variant of {RedfishClient::Connector} that caches GET responses.
|
7
|
-
class CachingConnector < Connector
|
8
|
-
# Create new caching connector.
|
9
|
-
#
|
10
|
-
# @param url [String] base url of the Redfish service
|
11
|
-
# @param verify [Boolean] verify SSL certificate of the service
|
12
|
-
def initialize(url, verify = true)
|
13
|
-
super
|
14
|
-
@cache = {}
|
15
|
-
end
|
16
|
-
|
17
|
-
# Issue GET request to service.
|
18
|
-
#
|
19
|
-
# Request is only issued if there is no cache entry for the existing path.
|
20
|
-
#
|
21
|
-
# @param path [String] path to the resource, relative to the base url
|
22
|
-
# @return [Excon::Response] response object
|
23
|
-
def get(path)
|
24
|
-
@cache[path] ||= super
|
25
|
-
end
|
26
|
-
|
27
|
-
# Clear the cached responses.
|
28
|
-
#
|
29
|
-
# Next GET request will repopulate the cache.
|
30
|
-
def reset(path: nil)
|
31
|
-
if path.nil?
|
32
|
-
@cache = {}
|
33
|
-
else
|
34
|
-
@cache.delete(path)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|