train-rest 0.1.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +108 -4
- data/lib/train-rest/auth_handler/anonymous.rb +1 -1
- data/lib/train-rest/auth_handler/basic.rb +6 -3
- data/lib/train-rest/auth_handler/redfish.rb +14 -4
- data/lib/train-rest/connection.rb +65 -9
- data/lib/train-rest/transport.rb +26 -1
- data/lib/train-rest/version.rb +1 -1
- data/train-rest.gemspec +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fd4a52ed234249cf9ae6c2de31b9883061ca31a9d34eb155a5f11e09b461b239
|
4
|
+
data.tar.gz: af4665c3c3637eb4c961f5419af4f145c85a2a60ff4125cfa9bc49800e21dae1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: db180b1416450c343f28be8d2223d5610b4a7ec75216d43f4343842250a47149bad30f5f2436e336bff1d3504e5cb817916ef44adf95ae9c65663f8cc56af0ef
|
7
|
+
data.tar.gz: 586b34568e078702c3ecabf1f40c6a61bdd4557cf95b953389813563f9d023d5239574d621870f319092e7d33bd436bfc8674a8ca8dc4635d52ef8564f125814
|
data/README.md
CHANGED
@@ -20,7 +20,7 @@ rake install:local
|
|
20
20
|
| Option | Explanation | Default |
|
21
21
|
| -------------------- | --------------------------------------- | ----------- |
|
22
22
|
| `endpoint` | Endpoint of the REST API | _required_ |
|
23
|
-
| `
|
23
|
+
| `verify_ssl` | Check certificate and chain | true |
|
24
24
|
| `auth_type` | Authentication type | `anonymous` |
|
25
25
|
| `debug_rest` | Enable debugging of HTTP traffic | false |
|
26
26
|
| `logger` | Alternative logging class | |
|
@@ -60,6 +60,9 @@ The Redfish standard is defined in <http://www.dmtf.org/standards/redfish> and
|
|
60
60
|
this handler does initial login, reuses the received session and logs out when
|
61
61
|
closing the transport cleanly.
|
62
62
|
|
63
|
+
Known vendors which implement RedFish based management for their systems include
|
64
|
+
HPE, Dell, IBM, SuperMicro, Lenovo, Huawei and others.
|
65
|
+
|
63
66
|
## Debugging and use in Chef
|
64
67
|
|
65
68
|
You can activate debugging by setting the `debug_rest` flag to `true'. Please
|
@@ -77,6 +80,46 @@ train = Train.create('rest', {
|
|
77
80
|
})
|
78
81
|
```
|
79
82
|
|
83
|
+
## Request Methods
|
84
|
+
|
85
|
+
This transport does not implement the `run_command` method, as there is no
|
86
|
+
line-based protocol to execute commands against. Instead, it implements its own
|
87
|
+
custom methods which suit REST interfaces. Trying to call this method will
|
88
|
+
throw an Exception.
|
89
|
+
|
90
|
+
### Generic Request
|
91
|
+
|
92
|
+
The `request` methods allows to send free-form requests against any defined or
|
93
|
+
custom methods.
|
94
|
+
|
95
|
+
`request(path, method = :get, request_parameters: {}, data: nil, headers: {},
|
96
|
+
json_processing: true)`
|
97
|
+
|
98
|
+
- `path`: The path to request, which will be appended to the `endpoint`
|
99
|
+
- `method`: The HTTP method in Ruby Symbol syntax
|
100
|
+
- `request_parameters`: A hash of parameters to the `rest-client` request
|
101
|
+
method for additional settings
|
102
|
+
- `data`: Data for actions like `:post` or `:put`. Not all methods accept
|
103
|
+
a data body.
|
104
|
+
- `headers`: Additional headers for the request
|
105
|
+
- `json_processing`: If the response is a JSON and you want to receive a
|
106
|
+
processed Hash/Array instead of text
|
107
|
+
|
108
|
+
For `request_parameters` and `headers`, there is data mixed in to add
|
109
|
+
authenticator responses, JSON processing etc. Please check the
|
110
|
+
implementation in `connection.rb` for details.
|
111
|
+
|
112
|
+
### Convenience Methods
|
113
|
+
|
114
|
+
Simplified wrappers are generated for the most common request types:
|
115
|
+
|
116
|
+
- `delete(path, request_parameters: {}, headers: {}, json_processing: true)`
|
117
|
+
- `head(path, request_parameters: {}, headers: {}, json_processing: true)`
|
118
|
+
- `get(path, request_parameters: {}, headers: {}, json_processing: true)`
|
119
|
+
- `post(path, request_parameters: {}, data: nil, headers: {}, json_processing: true)`
|
120
|
+
- `put(path, request_parameters: {}, data: nil, headers: {}, json_processing: true)`
|
121
|
+
- `patch(path, request_parameters: {}, data: nil, headers: {}, json_processing: true)`
|
122
|
+
|
80
123
|
## Example use
|
81
124
|
|
82
125
|
```ruby
|
@@ -119,15 +162,21 @@ conn = train.connection
|
|
119
162
|
conn.close
|
120
163
|
```
|
121
164
|
|
122
|
-
Example for logging into a RedFish based system
|
165
|
+
Example for logging into a RedFish based system. Please note that the RedFish
|
166
|
+
authentication handler will append `redfish/v1` to the endpoint automatically,
|
167
|
+
if it is not present.
|
168
|
+
|
169
|
+
Due to this, you can use RedFish systems either with a base URL like in the
|
170
|
+
example below or with a full one. Your own code needs to match the style
|
171
|
+
you choose.
|
123
172
|
|
124
173
|
```ruby
|
125
174
|
require 'train-rest'
|
126
175
|
|
127
176
|
# This will immediately do a login and add headers
|
128
177
|
train = Train.create('rest', {
|
129
|
-
endpoint: 'https://
|
130
|
-
|
178
|
+
endpoint: 'https://10.20.30.40',
|
179
|
+
verify_ssl: false,
|
131
180
|
|
132
181
|
auth_type: :redfish,
|
133
182
|
username: 'iloadmin',
|
@@ -140,3 +189,58 @@ conn = train.connection
|
|
140
189
|
# Handles logout as well
|
141
190
|
conn.close
|
142
191
|
```
|
192
|
+
|
193
|
+
## Use with Redfish, Your Custom Resources and Chef Target Mode
|
194
|
+
|
195
|
+
1. Set up a credentials file under `~/.chef/credentials` or `/etc/chef/credentials`:
|
196
|
+
|
197
|
+
```toml
|
198
|
+
['10.0.0.1']
|
199
|
+
endpoint = 'https://10.0.0.1/redfish/v1/'
|
200
|
+
username = 'user'
|
201
|
+
password = 'pass'
|
202
|
+
verify_ssl = false
|
203
|
+
auth_type = 'redfish'
|
204
|
+
```
|
205
|
+
|
206
|
+
1. Configure Chef to use the REST transport in `client.rb`:
|
207
|
+
|
208
|
+
```toml
|
209
|
+
target_mode.protocol = "rest"
|
210
|
+
```
|
211
|
+
|
212
|
+
1. Write your custom resources for REST APIs
|
213
|
+
1. Mark them up using the REST methods for target mode:
|
214
|
+
|
215
|
+
```ruby
|
216
|
+
provides :rest_resource, target_mode: true, platform: 'rest'
|
217
|
+
```
|
218
|
+
|
219
|
+
1. Run against the defiend targets via Chef Target Mode:
|
220
|
+
|
221
|
+
```shell
|
222
|
+
chef-client --local-mode --target 10.0.0.1 --runlist 'recipe[my-cookbook::setup]'
|
223
|
+
```
|
224
|
+
|
225
|
+
## Use with Prerecorded API responses
|
226
|
+
|
227
|
+
For testing during and after development, not all APIs can be used to verify your solution against.
|
228
|
+
The VCR gem offers the possibility to hook into web requests and intercept them to play back canned
|
229
|
+
responses.
|
230
|
+
|
231
|
+
Please read the documentation of the VCR gem on how to record your API and the concepts like
|
232
|
+
"cassettes", "libraries" and matchers.
|
233
|
+
|
234
|
+
The following options are available in train-rest for this:
|
235
|
+
|
236
|
+
| Option | Explanation | Default |
|
237
|
+
| -------------------- | --------------------------------------- | ------------ |
|
238
|
+
| `vcr_cassette` | Name of the response file | nil |
|
239
|
+
| `vcr_library` | Directory to search responses in | `vcr` |
|
240
|
+
| `vcr_match_on` | Elements to match request by | `method uri` |
|
241
|
+
| `vcr_record` | Recording mode | `none` |
|
242
|
+
| `vcr_hook_into` | Base library for intercepting | `webmock` |
|
243
|
+
|
244
|
+
VCR will only be required as a Gem and activated, if you supply a cassette name.
|
245
|
+
|
246
|
+
You can use all these settings in your Chef Target Mode `credentials` file as well.
|
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
require "base64" unless defined?(Base64)
|
2
|
+
|
3
|
+
require_relative "../auth_handler"
|
2
4
|
|
3
5
|
module TrainPlugins
|
4
6
|
module Rest
|
@@ -11,8 +13,9 @@ module TrainPlugins
|
|
11
13
|
|
12
14
|
def auth_parameters
|
13
15
|
{
|
14
|
-
|
15
|
-
|
16
|
+
headers: {
|
17
|
+
"Authorization" => format("Basic %s", Base64.encode64(options[:username] + ":" + options[:password]).chomp),
|
18
|
+
},
|
16
19
|
}
|
17
20
|
end
|
18
21
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require_relative "../auth_handler
|
1
|
+
require_relative "../auth_handler"
|
2
2
|
|
3
3
|
module TrainPlugins
|
4
4
|
module Rest
|
@@ -13,7 +13,7 @@ module TrainPlugins
|
|
13
13
|
|
14
14
|
def login
|
15
15
|
response = connection.post(
|
16
|
-
|
16
|
+
login_url,
|
17
17
|
headers: {
|
18
18
|
"Content-Type" => "application/json",
|
19
19
|
"OData-Version" => "4.0",
|
@@ -24,10 +24,11 @@ module TrainPlugins
|
|
24
24
|
}
|
25
25
|
)
|
26
26
|
|
27
|
-
raise StandardError.new("Authentication with Redfish failed") unless response.code === 201
|
28
|
-
|
29
27
|
@session_token = response.headers["x-auth-token"].first
|
30
28
|
@logout_url = response.headers["location"].first
|
29
|
+
|
30
|
+
rescue ::RestClient::RequestFailed => err
|
31
|
+
raise StandardError.new("Authentication with Redfish failed: " + err.message)
|
31
32
|
end
|
32
33
|
|
33
34
|
def logout
|
@@ -39,6 +40,15 @@ module TrainPlugins
|
|
39
40
|
|
40
41
|
{ "X-Auth-Token": @session_token }
|
41
42
|
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# Prepend the RedFish base, if not a global setting in the connection URL
|
47
|
+
def login_url
|
48
|
+
return "SessionService/Sessions/" if options[:endpoint].include?("redfish/v1")
|
49
|
+
|
50
|
+
"redfish/v1/SessionService/Sessions/"
|
51
|
+
end
|
42
52
|
end
|
43
53
|
end
|
44
54
|
end
|
@@ -1,9 +1,9 @@
|
|
1
|
-
require "json"
|
2
|
-
require "ostruct"
|
3
|
-
require "uri"
|
1
|
+
require "json" unless defined?(JSON)
|
2
|
+
require "ostruct" unless defined?(OpenStruct)
|
3
|
+
require "uri" unless defined?(URI)
|
4
4
|
|
5
|
-
require "rest-client"
|
6
|
-
require "train"
|
5
|
+
require "rest-client" unless defined?(RestClient)
|
6
|
+
require "train" unless defined?(Train)
|
7
7
|
|
8
8
|
module TrainPlugins
|
9
9
|
module Rest
|
@@ -13,9 +13,41 @@ module TrainPlugins
|
|
13
13
|
def initialize(options)
|
14
14
|
super(options)
|
15
15
|
|
16
|
+
# Plugin was called with an URI only
|
17
|
+
options[:endpoint] = options[:target].sub("rest://", "https://") unless options[:endpoint]
|
18
|
+
|
19
|
+
# Accept string (CLI) and boolean (API) options
|
20
|
+
options[:verify_ssl] = options[:verify_ssl].to_s == "false" ? false : true
|
21
|
+
|
22
|
+
setup_vcr
|
23
|
+
|
16
24
|
connect
|
17
25
|
end
|
18
26
|
|
27
|
+
def setup_vcr
|
28
|
+
return unless options[:vcr_cassette]
|
29
|
+
|
30
|
+
require "vcr" unless defined?(VCR)
|
31
|
+
|
32
|
+
# TODO: Starts from "/" :(
|
33
|
+
library = options[:vcr_library]
|
34
|
+
match_on = options[:vcr_match_on].split.map(&:to_sym)
|
35
|
+
|
36
|
+
VCR.configure do |config|
|
37
|
+
config.cassette_library_dir = library
|
38
|
+
config.hook_into options[:vcr_hook_into]
|
39
|
+
config.default_cassette_options = {
|
40
|
+
record: options[:vcr_record].to_sym,
|
41
|
+
match_requests_on: match_on,
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
VCR.insert_cassette options[:vcr_cassette]
|
46
|
+
rescue LoadError
|
47
|
+
logger.fatal "Install the vcr gem to use HTTP(S) playback capability"
|
48
|
+
raise
|
49
|
+
end
|
50
|
+
|
19
51
|
def connect
|
20
52
|
login if auth_handlers.include? auth_type
|
21
53
|
end
|
@@ -25,19 +57,38 @@ module TrainPlugins
|
|
25
57
|
end
|
26
58
|
|
27
59
|
def uri
|
28
|
-
components = URI
|
60
|
+
components = URI(options[:endpoint])
|
29
61
|
components.scheme = "rest"
|
30
62
|
components.to_s
|
31
63
|
end
|
32
64
|
|
65
|
+
# Allow overwriting to refine the type of REST API
|
66
|
+
attr_writer :detected_os
|
67
|
+
|
68
|
+
def inventory
|
69
|
+
OpenStruct.new({
|
70
|
+
name: @detected_os || "rest",
|
71
|
+
release: TrainPlugins::Rest::VERSION,
|
72
|
+
family_hierarchy: %w{rest api},
|
73
|
+
family: "api",
|
74
|
+
platform: "rest",
|
75
|
+
platform_version: 0,
|
76
|
+
})
|
77
|
+
end
|
78
|
+
|
79
|
+
alias os inventory
|
80
|
+
|
33
81
|
def platform
|
34
|
-
|
82
|
+
Train::Platforms.name("rest").in_family("api")
|
83
|
+
|
84
|
+
force_platform!("rest", { release: TrainPlugins::Rest::VERSION })
|
35
85
|
end
|
36
86
|
|
37
87
|
# User-faced API
|
38
88
|
|
39
89
|
%i{get post put patch delete head}.each do |method|
|
40
90
|
define_method(method) do |path, *parameters|
|
91
|
+
# TODO: "warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call"
|
41
92
|
request(path, method, *parameters)
|
42
93
|
end
|
43
94
|
end
|
@@ -49,8 +100,9 @@ module TrainPlugins
|
|
49
100
|
parameters[:url] = full_url(path)
|
50
101
|
|
51
102
|
if json_processing
|
103
|
+
parameters[:headers]["Accept"] = "application/json"
|
52
104
|
parameters[:headers]["Content-Type"] = "application/json"
|
53
|
-
parameters[:payload] = JSON.generate(data)
|
105
|
+
parameters[:payload] = JSON.generate(data) unless data.nil?
|
54
106
|
else
|
55
107
|
parameters[:payload] = data
|
56
108
|
end
|
@@ -100,7 +152,7 @@ module TrainPlugins
|
|
100
152
|
end
|
101
153
|
|
102
154
|
def auth_type
|
103
|
-
return options[:auth_type] if options[:auth_type]
|
155
|
+
return options[:auth_type].to_sym if options[:auth_type]
|
104
156
|
|
105
157
|
:basic if options[:username] && options[:password]
|
106
158
|
end
|
@@ -123,11 +175,15 @@ module TrainPlugins
|
|
123
175
|
end
|
124
176
|
|
125
177
|
def login
|
178
|
+
logger.info format("REST Login via %s authentication handler", auth_type.to_s) unless %i{anonymous basic}.include? auth_type
|
179
|
+
|
126
180
|
auth_handler.options = options
|
127
181
|
auth_handler.login
|
128
182
|
end
|
129
183
|
|
130
184
|
def logout
|
185
|
+
logger.info format("REST Logout via %s authentication handler", auth_type.to_s) unless %i{anonymous basic}.include? auth_type
|
186
|
+
|
131
187
|
auth_handler.logout
|
132
188
|
end
|
133
189
|
end
|
data/lib/train-rest/transport.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require "rubygems" unless defined?(Gem)
|
2
|
+
|
1
3
|
require "train-rest/connection"
|
2
4
|
|
3
5
|
module TrainPlugins
|
@@ -8,7 +10,7 @@ module TrainPlugins
|
|
8
10
|
option :endpoint, required: true
|
9
11
|
option :verify_ssl, default: true
|
10
12
|
option :proxy, default: nil
|
11
|
-
option :headers, default:
|
13
|
+
option :headers, default: {}
|
12
14
|
option :timeout, default: 120
|
13
15
|
|
14
16
|
option :auth_type, default: :anonymous
|
@@ -16,9 +18,32 @@ module TrainPlugins
|
|
16
18
|
option :password, default: nil
|
17
19
|
option :debug_rest, default: false
|
18
20
|
|
21
|
+
option :vcr_cassette, default: nil
|
22
|
+
option :vcr_library, default: "vcr"
|
23
|
+
option :vcr_match_on, default: "method uri"
|
24
|
+
option :vcr_record, default: "none"
|
25
|
+
option :vcr_hook_into, default: "webmock"
|
26
|
+
|
19
27
|
def connection(_instance_opts = nil)
|
28
|
+
dependency_checks
|
29
|
+
|
20
30
|
@connection ||= TrainPlugins::Rest::Connection.new(@options)
|
21
31
|
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def dependency_checks
|
36
|
+
return unless @options[:vcr_cassette]
|
37
|
+
|
38
|
+
raise Gem::LoadError.new("Install VCR Gem for API playback capability") unless gem_installed?("vcr")
|
39
|
+
|
40
|
+
stubber = @options[:vcr_hook_into]
|
41
|
+
raise Gem::LoadError.new("Install #{stubber} Gem for API playback capability") unless gem_installed?(stubber)
|
42
|
+
end
|
43
|
+
|
44
|
+
def gem_installed?(name)
|
45
|
+
Gem::Specification.find_all_by_name(name).any?
|
46
|
+
end
|
22
47
|
end
|
23
48
|
end
|
24
49
|
end
|
data/lib/train-rest/version.rb
CHANGED
data/train-rest.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: train-rest
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thomas Heinen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-10-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: train
|