train-rest 0.1.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f3aacbd72d0d4bb229c8db525be41ba6b6c8bb77b64a54856583a1857a52e81f
4
+ data.tar.gz: 8264209bde949d82b767a7f51934a48fbee744778a6982f042faf56ace809732
5
+ SHA512:
6
+ metadata.gz: 9b69087f1538a58e734943553f5cd4c738f47182fd3388d9855231309ed457db020c7a1f78517ff3a84bd8a6a5747be32b7051deb5a0ac7c30089b5fb403e396
7
+ data.tar.gz: 9c21069c170943fb1e93d42713388c1c4b5e04e143269d6dea8f0ce43ed61bd242c427d723a43cd110c79dba3c31ab2e112325b2b11463029caeb1e1d45e4bdd
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
@@ -0,0 +1,142 @@
1
+ # train-rest - Train transport
2
+
3
+ Provides a transport to communicate easily with RESTful APIs.
4
+
5
+ ## Requirements
6
+
7
+ - Gem `rest-client` in Version 2.1
8
+
9
+ ## Installation
10
+
11
+ You will have to build this gem yourself to install it as it is not yet on
12
+ Rubygems.Org. For this there is a rake task which makes this a one-liner:
13
+
14
+ ```bash
15
+ rake install:local
16
+ ```
17
+
18
+ ## Transport parameters
19
+
20
+ | Option | Explanation | Default |
21
+ | -------------------- | --------------------------------------- | ----------- |
22
+ | `endpoint` | Endpoint of the REST API | _required_ |
23
+ | `validate_ssl` | Check certificate and chain | true |
24
+ | `auth_type` | Authentication type | `anonymous` |
25
+ | `debug_rest` | Enable debugging of HTTP traffic | false |
26
+ | `logger` | Alternative logging class | |
27
+
28
+ ## Authenticators
29
+
30
+ ### Anonymous
31
+
32
+ Identifier: `auth_type: :anonymous`
33
+
34
+ No actions for authentication, logging in/out or session handing are made. This
35
+ assumes a public API.
36
+
37
+ ### Basic Authentication
38
+
39
+ Identifier: `auth_type: :basic`
40
+
41
+ | Option | Explanation | Default |
42
+ | -------------------- | --------------------------------------- | ----------- |
43
+ | `username` | Username for `basic` authentication | _required_ |
44
+ | `password` | Password for `basic` authentication | _required_ |
45
+
46
+ If you supply a `username` and a `password`, authentication will automatically
47
+ switch to `basic`.
48
+
49
+ ### Redfish
50
+
51
+ Identifier: `auth_type: :redfish`
52
+
53
+ | Option | Explanation | Default |
54
+ | -------------------- | --------------------------------------- | ----------- |
55
+ | `username` | Username for `redfish` authentication | _required_ |
56
+ | `password` | Password for `redfish` authentication | _required_ |
57
+
58
+ For access to integrated management controllers on standardized server hardware.
59
+ The Redfish standard is defined in <http://www.dmtf.org/standards/redfish> and
60
+ this handler does initial login, reuses the received session and logs out when
61
+ closing the transport cleanly.
62
+
63
+ ## Debugging and use in Chef
64
+
65
+ You can activate debugging by setting the `debug_rest` flag to `true'. Please
66
+ note, that this will also log any confidential parts of HTTP traffic as well.
67
+
68
+ For better integration into Chef custom resources, all REST debug output will
69
+ be printed on `info` level. This allows debugging Chef resources without
70
+ crawling through all other debug output:
71
+
72
+ ```ruby
73
+ train = Train.create('rest', {
74
+ endpoint: 'https://api.example.com/v1/',
75
+ debug_rest: true,
76
+ logger: Chef::Log
77
+ })
78
+ ```
79
+
80
+ ## Example use
81
+
82
+ ```ruby
83
+ require 'train-rest'
84
+
85
+ train = Train.create('rest', {
86
+ endpoint: 'https://api.example.com/v1/',
87
+
88
+ logger: Logger.new($stdout, level: :info)
89
+ })
90
+ conn = train.connection
91
+
92
+ # Get some hypothetical data
93
+ data = conn.get('device/1/settings')
94
+
95
+ # Modify + Patch
96
+ data['disabled'] = false
97
+ conn.patch('device/1/settings', data)
98
+
99
+ conn.close
100
+ ```
101
+
102
+ Example for basic authentication:
103
+
104
+ ```ruby
105
+ require 'train-rest'
106
+
107
+ # This will immediately do a login and add headers
108
+ train = Train.create('rest', {
109
+ endpoint: 'https://api.example.com/v1/',
110
+
111
+ auth_type: :basic,
112
+ username: 'admin',
113
+ password: '*********'
114
+ })
115
+ conn = train.connection
116
+
117
+ # ... do work, each request will resend Basic authentication headers ...
118
+
119
+ conn.close
120
+ ```
121
+
122
+ Example for logging into a RedFish based system:
123
+
124
+ ```ruby
125
+ require 'train-rest'
126
+
127
+ # This will immediately do a login and add headers
128
+ train = Train.create('rest', {
129
+ endpoint: 'https://api.example.com/v1/',
130
+ validate_ssl: false,
131
+
132
+ auth_type: :redfish,
133
+ username: 'iloadmin',
134
+ password: '*********'
135
+ })
136
+ conn = train.connection
137
+
138
+ # ... do work ...
139
+
140
+ # Handles logout as well
141
+ conn.close
142
+ ```
@@ -0,0 +1,11 @@
1
+ libdir = File.dirname(__FILE__)
2
+ $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
3
+
4
+ require "train-rest/version"
5
+
6
+ require "train-rest/transport"
7
+ require "train-rest/connection"
8
+
9
+ require "train-rest/auth_handler/anonymous"
10
+ require "train-rest/auth_handler/basic"
11
+ require "train-rest/auth_handler/redfish"
@@ -0,0 +1,61 @@
1
+ module TrainPlugins
2
+ module Rest
3
+ # Class to derive authentication handlers from.
4
+ class AuthHandler
5
+ attr_accessor :connection
6
+ attr_reader :options
7
+
8
+ def initialize(connection = nil)
9
+ @connection = connection
10
+ end
11
+
12
+ # Return name of handler
13
+ #
14
+ # @return [String]
15
+ def self.name
16
+ self.to_s.split("::").last.downcase
17
+ end
18
+
19
+ # Store authenticator options and trigger validation
20
+ #
21
+ # @param [Hash] All transport options
22
+ def options=(options)
23
+ @options = options
24
+
25
+ check_options
26
+ end
27
+
28
+ # Verify transport options
29
+ # @raise [ArgumentError] if options are not as needed
30
+ def check_options; end
31
+
32
+ # Handle Login
33
+ def login; end
34
+
35
+ # Handle Logout
36
+ def logout; end
37
+
38
+ # Headers added to the rest-client call
39
+ #
40
+ # @return [Hash]
41
+ def auth_headers
42
+ {}
43
+ end
44
+
45
+ # These will get added to the rest-client call.
46
+ #
47
+ # @return [Hash]
48
+ # @see https://www.rubydoc.info/gems/rest-client/RestClient/Request
49
+ def auth_parameters
50
+ { headers: auth_headers }
51
+ end
52
+
53
+ # List authentication handlers
54
+ #
55
+ # @return [Array] Classes derived from `AuthHandler`
56
+ def self.descendants
57
+ ObjectSpace.each_object(Class).select { |klass| klass < self }
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,8 @@
1
+ require_relative "../auth_handler.rb"
2
+
3
+ module TrainPlugins
4
+ module Rest
5
+ # No Authentication
6
+ class Anonymous < AuthHandler; end
7
+ end
8
+ end
@@ -0,0 +1,20 @@
1
+ require_relative "../auth_handler.rb"
2
+
3
+ module TrainPlugins
4
+ module Rest
5
+ # Authentication via HTTP Basic Authentication
6
+ class Basic < AuthHandler
7
+ def check_options
8
+ raise ArgumentError.new("Need username for Basic authentication") unless options[:username]
9
+ raise ArgumentError.new("Need password for Basic authentication") unless options[:password]
10
+ end
11
+
12
+ def auth_parameters
13
+ {
14
+ user: options[:username],
15
+ password: options[:password],
16
+ }
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,44 @@
1
+ require_relative "../auth_handler.rb"
2
+
3
+ module TrainPlugins
4
+ module Rest
5
+ # Authentication and Session handling for the Redfish 1.0 API
6
+ #
7
+ # @see http://www.dmtf.org/standards/redfish
8
+ class Redfish < AuthHandler
9
+ def check_options
10
+ raise ArgumentError.new("Need username for Redfish authentication") unless options[:username]
11
+ raise ArgumentError.new("Need password for Redfish authentication") unless options[:password]
12
+ end
13
+
14
+ def login
15
+ response = connection.post(
16
+ "SessionService/Sessions/",
17
+ headers: {
18
+ "Content-Type" => "application/json",
19
+ "OData-Version" => "4.0",
20
+ },
21
+ data: {
22
+ "UserName" => options[:username],
23
+ "Password" => options[:password],
24
+ }
25
+ )
26
+
27
+ raise StandardError.new("Authentication with Redfish failed") unless response.code === 201
28
+
29
+ @session_token = response.headers["x-auth-token"].first
30
+ @logout_url = response.headers["location"].first
31
+ end
32
+
33
+ def logout
34
+ connection.delete(@logout_url)
35
+ end
36
+
37
+ def auth_headers
38
+ return {} unless @session_token
39
+
40
+ { "X-Auth-Token": @session_token }
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,135 @@
1
+ require "json"
2
+ require "ostruct"
3
+ require "uri"
4
+
5
+ require "rest-client"
6
+ require "train"
7
+
8
+ module TrainPlugins
9
+ module Rest
10
+ class Connection < Train::Plugins::Transport::BaseConnection
11
+ attr_reader :options
12
+
13
+ def initialize(options)
14
+ super(options)
15
+
16
+ connect
17
+ end
18
+
19
+ def connect
20
+ login if auth_handlers.include? auth_type
21
+ end
22
+
23
+ def close
24
+ logout if auth_handlers.include? auth_type
25
+ end
26
+
27
+ def uri
28
+ components = URI.new(options[:endpoint])
29
+ components.scheme = "rest"
30
+ components.to_s
31
+ end
32
+
33
+ def platform
34
+ force_platform!("rest", { endpoint: uri })
35
+ end
36
+
37
+ # User-faced API
38
+
39
+ %i{get post put patch delete head}.each do |method|
40
+ define_method(method) do |path, *parameters|
41
+ request(path, method, *parameters)
42
+ end
43
+ end
44
+
45
+ def request(path, method = :get, request_parameters: {}, data: nil, headers: {}, json_processing: true)
46
+ parameters = global_parameters.merge(request_parameters)
47
+
48
+ parameters[:method] = method
49
+ parameters[:url] = full_url(path)
50
+
51
+ if json_processing
52
+ parameters[:headers]["Content-Type"] = "application/json"
53
+ parameters[:payload] = JSON.generate(data)
54
+ else
55
+ parameters[:payload] = data
56
+ end
57
+
58
+ parameters[:headers].merge! headers
59
+ parameters.compact!
60
+
61
+ logger.info format("[REST] => %s", parameters.to_s) if options[:debug_rest]
62
+ response = RestClient::Request.execute(parameters)
63
+
64
+ logger.info format("[REST] <= %s", response.to_s) if options[:debug_rest]
65
+ transform_response(response, json_processing)
66
+ end
67
+
68
+ # Auth Handlers-faced API
69
+
70
+ def auth_parameters
71
+ auth_handler.auth_parameters
72
+ end
73
+
74
+ private
75
+
76
+ def global_parameters
77
+ params = {
78
+ verify_ssl: options[:verify_ssl] ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE,
79
+ timeout: options[:timeout],
80
+ proxy: options[:proxy],
81
+ headers: options[:headers],
82
+ }
83
+
84
+ params.merge!(auth_parameters)
85
+
86
+ params
87
+ end
88
+
89
+ def full_url(path)
90
+ (URI(@options[:endpoint]) + path).to_s
91
+ end
92
+
93
+ def transform_response(response, json_processing = true)
94
+ OpenStruct.new(
95
+ code: response.code,
96
+ headers: response.raw_headers,
97
+ data: json_processing ? JSON.load(response.to_s) : response.to_s,
98
+ duration: response.duration
99
+ )
100
+ end
101
+
102
+ def auth_type
103
+ return options[:auth_type] if options[:auth_type]
104
+
105
+ :basic if options[:username] && options[:password]
106
+ end
107
+
108
+ attr_writer :auth_handler
109
+
110
+ def auth_handler_classes
111
+ AuthHandler.descendants
112
+ end
113
+
114
+ def auth_handlers
115
+ auth_handler_classes.map { |handler| handler.name.to_sym }
116
+ end
117
+
118
+ def auth_handler
119
+ desired_handler = auth_handler_classes.detect { |handler| handler.name == auth_type.to_s }
120
+ raise NameError.new(format("Authentication handler %s not found", auth_type.to_s)) unless desired_handler
121
+
122
+ @auth_handler ||= desired_handler.new(self)
123
+ end
124
+
125
+ def login
126
+ auth_handler.options = options
127
+ auth_handler.login
128
+ end
129
+
130
+ def logout
131
+ auth_handler.logout
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,24 @@
1
+ require "train-rest/connection"
2
+
3
+ module TrainPlugins
4
+ module Rest
5
+ class Transport < Train.plugin(1)
6
+ name "rest"
7
+
8
+ option :endpoint, required: true
9
+ option :verify_ssl, default: true
10
+ option :proxy, default: nil
11
+ option :headers, default: nil
12
+ option :timeout, default: 120
13
+
14
+ option :auth_type, default: :anonymous
15
+ option :username, default: nil
16
+ option :password, default: nil
17
+ option :debug_rest, default: false
18
+
19
+ def connection(_instance_opts = nil)
20
+ @connection ||= TrainPlugins::Rest::Connection.new(@options)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,5 @@
1
+ module TrainPlugins
2
+ module Rest
3
+ VERSION = "0.1.0".freeze
4
+ end
5
+ end
@@ -0,0 +1,30 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "train-rest/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "train-rest"
7
+ spec.version = TrainPlugins::Rest::VERSION
8
+ spec.authors = ["Thomas Heinen"]
9
+ spec.email = ["theinen@tecracer.de"]
10
+ spec.summary = "Train transport for REST"
11
+ spec.description = "Provides a transport to communicate easily with RESTful APIs."
12
+ spec.homepage = "https://github.com/sgre-chef/train-rest"
13
+ spec.license = "Apache-2.0"
14
+
15
+ spec.files = %w{
16
+ README.md train-rest.gemspec Gemfile
17
+ } + Dir.glob(
18
+ "lib/**/*", File::FNM_DOTMATCH
19
+ ).reject { |f| File.directory?(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency "train", "~> 3.0"
23
+ spec.add_dependency "rest-client", "~> 2.1"
24
+
25
+ spec.add_development_dependency "bump", "~> 0.9"
26
+ spec.add_development_dependency "chefstyle", "~> 0.14"
27
+ spec.add_development_dependency "guard", "~> 2.16"
28
+ spec.add_development_dependency "mdl", "~> 0.9"
29
+ spec.add_development_dependency "rake", "~> 13.0"
30
+ end
metadata ADDED
@@ -0,0 +1,152 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: train-rest
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Thomas Heinen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-05-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: train
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rest-client
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bump
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.9'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.9'
55
+ - !ruby/object:Gem::Dependency
56
+ name: chefstyle
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.14'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.14'
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.16'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.16'
83
+ - !ruby/object:Gem::Dependency
84
+ name: mdl
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.9'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.9'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '13.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '13.0'
111
+ description: Provides a transport to communicate easily with RESTful APIs.
112
+ email:
113
+ - theinen@tecracer.de
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - Gemfile
119
+ - README.md
120
+ - lib/train-rest.rb
121
+ - lib/train-rest/auth_handler.rb
122
+ - lib/train-rest/auth_handler/anonymous.rb
123
+ - lib/train-rest/auth_handler/basic.rb
124
+ - lib/train-rest/auth_handler/redfish.rb
125
+ - lib/train-rest/connection.rb
126
+ - lib/train-rest/transport.rb
127
+ - lib/train-rest/version.rb
128
+ - train-rest.gemspec
129
+ homepage: https://github.com/sgre-chef/train-rest
130
+ licenses:
131
+ - Apache-2.0
132
+ metadata: {}
133
+ post_install_message:
134
+ rdoc_options: []
135
+ require_paths:
136
+ - lib
137
+ required_ruby_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ required_rubygems_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ requirements: []
148
+ rubygems_version: 3.0.3
149
+ signing_key:
150
+ specification_version: 4
151
+ summary: Train transport for REST
152
+ test_files: []