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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f3aacbd72d0d4bb229c8db525be41ba6b6c8bb77b64a54856583a1857a52e81f
4
- data.tar.gz: 8264209bde949d82b767a7f51934a48fbee744778a6982f042faf56ace809732
3
+ metadata.gz: fd4a52ed234249cf9ae6c2de31b9883061ca31a9d34eb155a5f11e09b461b239
4
+ data.tar.gz: af4665c3c3637eb4c961f5419af4f145c85a2a60ff4125cfa9bc49800e21dae1
5
5
  SHA512:
6
- metadata.gz: 9b69087f1538a58e734943553f5cd4c738f47182fd3388d9855231309ed457db020c7a1f78517ff3a84bd8a6a5747be32b7051deb5a0ac7c30089b5fb403e396
7
- data.tar.gz: 9c21069c170943fb1e93d42713388c1c4b5e04e143269d6dea8f0ce43ed61bd242c427d723a43cd110c79dba3c31ab2e112325b2b11463029caeb1e1d45e4bdd
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
- | `validate_ssl` | Check certificate and chain | true |
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://api.example.com/v1/',
130
- validate_ssl: false,
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,4 @@
1
- require_relative "../auth_handler.rb"
1
+ require_relative "../auth_handler"
2
2
 
3
3
  module TrainPlugins
4
4
  module Rest
@@ -1,4 +1,6 @@
1
- require_relative "../auth_handler.rb"
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
- user: options[:username],
15
- password: options[:password],
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.rb"
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
- "SessionService/Sessions/",
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.new(options[:endpoint])
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
- force_platform!("rest", { endpoint: uri })
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
@@ -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: nil
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
@@ -1,5 +1,5 @@
1
1
  module TrainPlugins
2
2
  module Rest
3
- VERSION = "0.1.0".freeze
3
+ VERSION = "0.3.1".freeze
4
4
  end
5
5
  end
data/train-rest.gemspec CHANGED
@@ -1,4 +1,4 @@
1
- lib = File.expand_path("../lib", __FILE__)
1
+ lib = File.expand_path("lib", __dir__)
2
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
3
  require "train-rest/version"
4
4
 
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.0
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: 2020-05-07 00:00:00.000000000 Z
11
+ date: 2021-10-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: train