train-rest 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []