oneview-sdk 1.0.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.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gitattributes +2 -0
  3. data/.gitignore +29 -0
  4. data/.rubocop.yml +73 -0
  5. data/.travis.yml +8 -0
  6. data/CHANGELOG.md +39 -0
  7. data/Gemfile +2 -0
  8. data/LICENSE +201 -0
  9. data/README.md +317 -0
  10. data/Rakefile +90 -0
  11. data/bin/oneview-sdk-ruby +4 -0
  12. data/lib/oneview-sdk.rb +9 -0
  13. data/lib/oneview-sdk/cli.rb +407 -0
  14. data/lib/oneview-sdk/client.rb +163 -0
  15. data/lib/oneview-sdk/config_loader.rb +20 -0
  16. data/lib/oneview-sdk/resource.rb +313 -0
  17. data/lib/oneview-sdk/resource/enclosure.rb +169 -0
  18. data/lib/oneview-sdk/resource/enclosure_group.rb +98 -0
  19. data/lib/oneview-sdk/resource/ethernet_network.rb +60 -0
  20. data/lib/oneview-sdk/resource/fc_network.rb +31 -0
  21. data/lib/oneview-sdk/resource/fcoe_network.rb +25 -0
  22. data/lib/oneview-sdk/resource/firmware_bundle.rb +37 -0
  23. data/lib/oneview-sdk/resource/firmware_driver.rb +21 -0
  24. data/lib/oneview-sdk/resource/interconnect.rb +87 -0
  25. data/lib/oneview-sdk/resource/lig_uplink_set.rb +86 -0
  26. data/lib/oneview-sdk/resource/logical_enclosure.rb +84 -0
  27. data/lib/oneview-sdk/resource/logical_interconnect.rb +283 -0
  28. data/lib/oneview-sdk/resource/logical_interconnect_group.rb +92 -0
  29. data/lib/oneview-sdk/resource/server_hardware.rb +88 -0
  30. data/lib/oneview-sdk/resource/server_hardware_type.rb +27 -0
  31. data/lib/oneview-sdk/resource/server_profile.rb +37 -0
  32. data/lib/oneview-sdk/resource/server_profile_template.rb +24 -0
  33. data/lib/oneview-sdk/resource/storage_pool.rb +41 -0
  34. data/lib/oneview-sdk/resource/storage_system.rb +63 -0
  35. data/lib/oneview-sdk/resource/uplink_set.rb +119 -0
  36. data/lib/oneview-sdk/resource/volume.rb +188 -0
  37. data/lib/oneview-sdk/resource/volume_snapshot.rb +27 -0
  38. data/lib/oneview-sdk/resource/volume_template.rb +106 -0
  39. data/lib/oneview-sdk/rest.rb +163 -0
  40. data/lib/oneview-sdk/ssl_helper.rb +75 -0
  41. data/lib/oneview-sdk/version.rb +4 -0
  42. data/oneview-sdk.gemspec +31 -0
  43. metadata +204 -0
data/Rakefile ADDED
@@ -0,0 +1,90 @@
1
+ require 'bundler'
2
+ require 'bundler/gem_tasks'
3
+ require 'bundler/setup'
4
+ require 'rspec/core/rake_task'
5
+ require 'rubocop/rake_task'
6
+
7
+ task default: :spec
8
+ spec_pattern = 'spec/**/*_spec.rb'
9
+ def_spec_options = '--color '
10
+ def_int_spec_options = '-f d --color '
11
+
12
+ desc 'Run unit tests only'
13
+ RSpec::Core::RakeTask.new(:spec) do |spec|
14
+ spec.pattern = spec_pattern
15
+ spec.rspec_opts = def_spec_options
16
+ spec.rspec_opts << ' --tag ~integration'
17
+ spec.rspec_opts << ' --tag ~system'
18
+ end
19
+
20
+ desc 'Run integration tests only'
21
+ RSpec::Core::RakeTask.new('spec:integration') do |spec|
22
+ spec.pattern = spec_pattern
23
+ spec.rspec_opts = def_int_spec_options
24
+ spec.rspec_opts << ' --tag integration'
25
+ end
26
+
27
+ desc 'Run integration creation tests only'
28
+ RSpec::Core::RakeTask.new('spec:integration:create') do |spec|
29
+ spec.pattern = 'spec/**/*create_spec.rb'
30
+ spec.rspec_opts = def_int_spec_options
31
+ spec.rspec_opts << ' --tag integration'
32
+ end
33
+
34
+ desc 'Run integration update tests only'
35
+ RSpec::Core::RakeTask.new('spec:integration:update') do |spec|
36
+ spec.pattern = 'spec/**/*update_spec.rb'
37
+ spec.rspec_opts = def_int_spec_options
38
+ spec.rspec_opts << ' --tag integration'
39
+ end
40
+
41
+ desc 'Run integration deletion tests only'
42
+ RSpec::Core::RakeTask.new('spec:integration:delete') do |spec|
43
+ spec.pattern = 'spec/**/*delete_spec.rb'
44
+ spec.rspec_opts = def_int_spec_options
45
+ spec.rspec_opts << ' --tag integration'
46
+ end
47
+
48
+ desc 'Run System tests'
49
+ RSpec::Core::RakeTask.new('spec:system') do |spec|
50
+ spec.pattern = 'spec/**/*_spec.rb'
51
+ spec.rspec_opts = def_int_spec_options
52
+ spec.rspec_opts << ' --tag system'
53
+ end
54
+
55
+ desc 'Run System tests Light Profile'
56
+ RSpec::Core::RakeTask.new('spec:system:light') do |spec|
57
+ spec.pattern = 'spec/system/light_profile/*_spec.rb'
58
+ spec.rspec_opts = def_int_spec_options
59
+ spec.rspec_opts << ' --tag system'
60
+ end
61
+
62
+ desc 'Run System tests Medium Profile'
63
+ RSpec::Core::RakeTask.new('spec:system:medium') do |spec|
64
+ spec.pattern = 'spec/system/medium_profile/*_spec.rb'
65
+ spec.rspec_opts = def_int_spec_options
66
+ spec.rspec_opts << ' --tag system'
67
+ end
68
+
69
+ desc 'Run System tests Heavy Profile'
70
+ RSpec::Core::RakeTask.new('spec:system:heavy') do |spec|
71
+ spec.pattern = 'spec/system/heavy_profile/*_spec.rb'
72
+ spec.rspec_opts = def_int_spec_options
73
+ spec.rspec_opts << ' --tag system'
74
+ end
75
+
76
+ RuboCop::RakeTask.new
77
+
78
+ desc 'Runs rubocop and unit tests'
79
+ task :test do
80
+ Rake::Task[:rubocop].invoke
81
+ Rake::Task[:spec].invoke
82
+ end
83
+
84
+ desc 'Run rubocop, unit & integration tests'
85
+ task 'test:all' do
86
+ Rake::Task[:rubocop].invoke
87
+ Rake::Task[:spec].invoke
88
+ Rake::Task['spec:integration'].invoke
89
+ Rake::Task['spec:system'].invoke
90
+ end
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'oneview-sdk'
3
+
4
+ OneviewSDK::Cli::Runner.new(ARGV.dup).execute!
@@ -0,0 +1,9 @@
1
+ require_relative 'oneview-sdk/version'
2
+ require_relative 'oneview-sdk/client'
3
+ require_relative 'oneview-sdk/resource'
4
+ require_relative 'oneview-sdk/cli'
5
+
6
+ # Module for interracting with the HPE OneView API
7
+ module OneviewSDK
8
+ ENV_VARS = %w(ONEVIEWSDK_URL ONEVIEWSDK_USER ONEVIEWSDK_PASSWORD ONEVIEWSDK_TOKEN ONEVIEWSDK_SSL_ENABLED).freeze
9
+ end
@@ -0,0 +1,407 @@
1
+ require 'thor'
2
+ require 'json'
3
+ require 'yaml'
4
+ require 'highline/import'
5
+
6
+ module OneviewSDK
7
+ # cli for oneview-sdk
8
+ class Cli < Thor
9
+ # Runner class to enable testing
10
+ class Runner
11
+ def initialize(argv, stdin = STDIN, stdout = STDOUT, stderr = STDERR, kernel = Kernel)
12
+ @argv = argv
13
+ @stdin = stdin
14
+ @stdout = stdout
15
+ @stderr = stderr
16
+ @kernel = kernel
17
+ end
18
+
19
+ def execute!
20
+ exit_code = begin
21
+ $stderr = @stderr
22
+ $stdin = @stdin
23
+ $stdout = @stdout
24
+
25
+ OneviewSDK::Cli.start(@argv)
26
+ 0
27
+ rescue StandardError => e
28
+ b = e.backtrace
29
+ @stderr.puts("#{b.shift}: #{e.message} (#{e.class})")
30
+ @stderr.puts(b.map { |s| "\tfrom #{s}" }.join("\n"))
31
+ 1
32
+ rescue SystemExit => e
33
+ e.status
34
+ end
35
+
36
+ # Proxy our exit code back to the injected kernel.
37
+ @kernel.exit(exit_code)
38
+ end
39
+ end
40
+
41
+ class_option :ssl_verify,
42
+ type: :boolean,
43
+ desc: 'Enable/Disable SSL verification for requests. Can also use ENV[\'ONEVIEWSDK_SSL_ENABLED\']',
44
+ default: nil
45
+
46
+ class_option :url,
47
+ desc: 'URL of OneView appliance. Can also use ENV[\'ONEVIEWSDK_URL\']',
48
+ aliases: '-u'
49
+
50
+ class_option :api_version,
51
+ type: :numeric,
52
+ banner: 'VERSION',
53
+ desc: 'API version to use'
54
+
55
+ class_option :log_level,
56
+ desc: 'Log level to use',
57
+ aliases: '-l',
58
+ enum: %w(debug info warn error),
59
+ default: 'warn'
60
+
61
+ map ['-v', '--version'] => :version
62
+
63
+
64
+ desc 'console', 'Open a Ruby console with a connection to OneView'
65
+ def console
66
+ client_setup({}, true, true)
67
+ puts "Console Connected to #{@client.url}"
68
+ puts "HINT: The @client object is available to you\n\n"
69
+ rescue
70
+ puts "WARNING: Couldn't connect to #{@options['url'] || ENV['ONEVIEWSDK_URL']}\n\n"
71
+ ensure
72
+ require 'pry'
73
+ Pry.config.prompt = proc { '> ' }
74
+ Pry.plugins['stack_explorer'] && Pry.plugins['stack_explorer'].disable!
75
+ Pry.plugins['byebug'] && Pry.plugins['byebug'].disable!
76
+ Pry.start(OneviewSDK::Console.new(@client))
77
+ end
78
+
79
+ desc 'version', 'Print gem and OneView appliance versions'
80
+ def version
81
+ puts "Gem Version: #{OneviewSDK::VERSION}"
82
+ client_setup({ 'log_level' => :error }, true)
83
+ puts "OneView appliance API version at '#{@client.url}' = #{@client.max_api_version}"
84
+ rescue StandardError, SystemExit
85
+ puts 'OneView appliance API version unknown'
86
+ end
87
+
88
+ method_option :format,
89
+ desc: 'Output format',
90
+ aliases: '-f',
91
+ enum: %w(json yaml human),
92
+ default: 'human'
93
+ desc 'env', 'Show environment variables for oneview-sdk-ruby'
94
+ def env
95
+ data = {}
96
+ OneviewSDK::ENV_VARS.each { |k| data[k] = ENV[k] }
97
+ if @options['format'] == 'human'
98
+ data.each do |key, value|
99
+ value = "'#{value}'" if value && ! %w(true false).include?(value)
100
+ printf "%-#{data.keys.max_by(&:length).length}s = %s\n", key, value || 'nil'
101
+ end
102
+ else
103
+ output(parse_hash(data, true))
104
+ end
105
+ end
106
+
107
+ desc 'login', 'Attempt authentication and return token'
108
+ def login
109
+ client_setup
110
+ puts "Login Successful! Token = #{@client.token}"
111
+ end
112
+
113
+ method_option :format,
114
+ desc: 'Output format',
115
+ aliases: '-f',
116
+ enum: %w(json yaml human),
117
+ default: 'human'
118
+ desc 'list TYPE', 'List names of resources'
119
+ def list(type)
120
+ resource_class = parse_type(type)
121
+ client_setup
122
+ data = []
123
+ resource_class.get_all(@client).each { |r| data.push(r[:name]) }
124
+ output data
125
+ end
126
+
127
+ method_option :format,
128
+ desc: 'Output format',
129
+ aliases: '-f',
130
+ enum: %w(json yaml human),
131
+ default: 'human'
132
+ method_option :attribute,
133
+ type: :string,
134
+ desc: 'Comma-seperated list of attributes to show',
135
+ aliases: '-a'
136
+ desc 'show TYPE NAME', 'Show resource details'
137
+ def show(type, name)
138
+ resource_class = parse_type(type)
139
+ client_setup
140
+ matches = resource_class.find_by(@client, name: name)
141
+ fail_nice 'Not Found' if matches.empty?
142
+ data = matches.first.data
143
+ if options['attribute']
144
+ new_data = {}
145
+ options['attribute'].split(',').each do |attr|
146
+ new_data[attr] = data[attr]
147
+ end
148
+ data = new_data
149
+ end
150
+ output data
151
+ end
152
+
153
+ method_option :format,
154
+ desc: 'Output format',
155
+ aliases: '-f',
156
+ enum: %w(json yaml human),
157
+ default: 'human'
158
+ method_option :attribute,
159
+ type: :string,
160
+ desc: 'Comma-seperated list of attributes to show',
161
+ aliases: '-a'
162
+ method_option :filter,
163
+ type: :hash,
164
+ desc: 'Hash of key/value pairs to filter on',
165
+ required: true
166
+ desc 'search TYPE', 'Search for resource by key/value pair(s)'
167
+ def search(type)
168
+ resource_class = parse_type(type)
169
+ client_setup
170
+ filter = parse_hash(options['filter'])
171
+ matches = resource_class.find_by(@client, filter)
172
+ if matches.empty? # Search with integers & booleans converted
173
+ filter = parse_hash(options['filter'], true)
174
+ matches = resource_class.find_by(@client, filter) unless filter == options['filter']
175
+ end
176
+ if options['attribute']
177
+ data = []
178
+ matches.each do |d|
179
+ temp = {}
180
+ options['attribute'].split(',').each do |attr|
181
+ temp[attr] = d[attr]
182
+ end
183
+ data.push temp
184
+ end
185
+ output data
186
+ else # List names only by default
187
+ names = []
188
+ matches.each { |m| names.push(m['name']) }
189
+ output names
190
+ end
191
+ end
192
+
193
+ method_option :force,
194
+ desc: 'Delete without confirmation',
195
+ type: :boolean,
196
+ aliases: '-f'
197
+ desc 'delete TYPE NAME', 'Delete resource by name'
198
+ def delete(type, name)
199
+ resource_class = parse_type(type)
200
+ client_setup
201
+ matches = resource_class.find_by(@client, name: name)
202
+ fail_nice 'Not Found' if matches.empty?
203
+ resource = matches.first
204
+ return unless options['force'] || agree("Delete '#{name}'? [Y/N] ")
205
+ begin
206
+ resource.delete
207
+ output 'Deleted Successfully!'
208
+ rescue StandardError => e
209
+ fail_nice "Failed to delete #{resource.class.name.split('::').last} '#{name}': #{e}"
210
+ end
211
+ end
212
+
213
+ method_option :force,
214
+ desc: 'Delete without confirmation',
215
+ type: :boolean,
216
+ aliases: '-f'
217
+ desc 'delete_from_file FILE_PATH', 'Delete resource defined in file'
218
+ def delete_from_file(file_path)
219
+ client_setup
220
+ resource = OneviewSDK::Resource.from_file(@client, file_path)
221
+ fail_nice 'File must define name or uri' unless resource[:name] || resource[:uri]
222
+ found = resource.retrieve! rescue false
223
+ found ||= resource.refresh rescue false
224
+ fail_nice "#{resource.class.name.split('::').last} '#{resource[:name] || resource[:uri]}' Not Found" unless found
225
+ unless options['force'] || agree("Delete '#{resource[:name]}'? [Y/N] ")
226
+ puts 'OK, exiting.'
227
+ return
228
+ end
229
+ begin
230
+ resource.delete
231
+ output 'Deleted Successfully!'
232
+ rescue StandardError => e
233
+ fail_nice "Failed to delete #{resource.class.name.split('::').last} '#{resource[:name]}': #{e}"
234
+ end
235
+ end
236
+
237
+ method_option :force,
238
+ desc: 'Overwrite without confirmation',
239
+ type: :boolean,
240
+ aliases: '-f'
241
+ method_option :if_missing,
242
+ desc: 'Only create if missing (Don\'t update)',
243
+ type: :boolean,
244
+ aliases: '-i'
245
+ desc 'create_from_file FILE_PATH', 'Create/Overwrite resource defined in file'
246
+ def create_from_file(file_path)
247
+ fail_nice "Can't use the 'force' and 'if_missing' flags at the same time." if options['force'] && options['if_missing']
248
+ client_setup
249
+ resource = OneviewSDK::Resource.from_file(@client, file_path)
250
+ resource[:uri] = nil
251
+ fail_nice 'File must specify a resource name' unless resource[:name]
252
+ existing_resource = resource.class.find_by(@client, name: resource[:name]).first
253
+ if existing_resource
254
+ if options['if_missing']
255
+ puts "Skipped: '#{resource[:name]}': #{resource.class.name.split('::').last} already exists."
256
+ return
257
+ end
258
+ fail_nice "#{resource.class.name.split('::').last} '#{resource[:name]}' already exists." unless options['force']
259
+ begin
260
+ resource.data.delete('uri')
261
+ existing_resource.update(resource.data)
262
+ output "Updated Successfully!\n#{resource[:uri]}"
263
+ rescue StandardError => e
264
+ fail_nice "Failed to update #{resource.class.name.split('::').last} '#{resource[:name]}': #{e}"
265
+ end
266
+ else
267
+ begin
268
+ resource.create
269
+ output "Created Successfully!\n#{resource[:uri]}"
270
+ rescue StandardError => e
271
+ fail_nice "Failed to create #{resource.class.name.split('::').last} '#{resource[:name]}': #{e}"
272
+ end
273
+ end
274
+ end
275
+
276
+ desc 'cert check|import|list URL', 'Check, import, or list OneView certs'
277
+ def cert(type, url = ENV['ONEVIEWSDK_URL'])
278
+ case type.downcase
279
+ when 'check'
280
+ fail_nice 'Must specify a url' unless url
281
+ puts "Checking certificate for '#{url}' ..."
282
+ if OneviewSDK::SSLHelper.check_cert(url)
283
+ puts 'Certificate is valid!'
284
+ else
285
+ fail_nice 'Certificate Validation Failed!'
286
+ end
287
+ when 'import'
288
+ fail_nice 'Must specify a url' unless url
289
+ puts "Importing certificate for '#{url}' into '#{OneviewSDK::SSLHelper::CERT_STORE}'..."
290
+ OneviewSDK::SSLHelper.install_cert(url)
291
+ when 'list'
292
+ if File.file?(OneviewSDK::SSLHelper::CERT_STORE)
293
+ puts File.read(OneviewSDK::SSLHelper::CERT_STORE)
294
+ else
295
+ puts 'No certs imported!'
296
+ end
297
+ else fail_nice "Invalid action '#{type}'. Valid actions are [check, import, list]"
298
+ end
299
+ rescue StandardError => e
300
+ fail_nice e.message
301
+ end
302
+
303
+ private
304
+
305
+ def fail_nice(msg = nil)
306
+ puts "ERROR: #{msg}" if msg
307
+ exit 1
308
+ end
309
+
310
+ def client_setup(client_params = {}, quiet = false, throw_errors = false)
311
+ client_params['ssl_enabled'] = true if @options['ssl_verify'] == true
312
+ client_params['ssl_enabled'] = false if @options['ssl_verify'] == false
313
+ client_params['url'] ||= @options['url'] if @options['url']
314
+ client_params['log_level'] ||= @options['log_level'].to_sym if @options['log_level']
315
+ @client = OneviewSDK::Client.new(client_params)
316
+ rescue StandardError => e
317
+ raise e if throw_errors
318
+ fail_nice if quiet
319
+ fail_nice "Failed to login to OneView appliance at '#{client_params['url']}'. Message: #{e}"
320
+ end
321
+
322
+ # Get resource class from given string
323
+ def parse_type(type)
324
+ valid_classes = []
325
+ ObjectSpace.each_object(Class).select { |klass| klass < OneviewSDK::Resource }.each do |c|
326
+ valid_classes.push(c.name.split('::').last)
327
+ end
328
+ OneviewSDK.resource_named(type) || fail_nice("Invalid resource type: '#{type}'.\n Valid options are #{valid_classes}")
329
+ end
330
+
331
+ # Parse options hash from input. Handles chaining and keywords such as true/false & nil
332
+ # Returns new hash with proper nesting and formatting
333
+ def parse_hash(hash, convert_types = false)
334
+ new_hash = {}
335
+ hash.each do |k, v|
336
+ if convert_types
337
+ v = v.to_i if v && v.match(/^\d+$/)
338
+ v = true if v == 'true'
339
+ v = false if v == 'false'
340
+ v = nil if v == 'nil'
341
+ end
342
+ if k =~ /\./
343
+ sub_hash = new_hash
344
+ split = k.split('.')
345
+ split.each do |sub_key|
346
+ if sub_key == split.last
347
+ sub_hash[sub_key] = v
348
+ else
349
+ sub_hash[sub_key] ||= {}
350
+ sub_hash = sub_hash[sub_key]
351
+ end
352
+ end
353
+ new_hash[split.first] ||= {}
354
+ else
355
+ new_hash[k] = v
356
+ end
357
+ end
358
+ new_hash
359
+ end
360
+
361
+ # Print output in a given format.
362
+ def output(data = {}, indent = 0)
363
+ case @options['format']
364
+ when 'json'
365
+ puts JSON.pretty_generate(data)
366
+ when 'yaml'
367
+ puts data.to_yaml
368
+ else
369
+ if data.class == Hash || data.class <= OneviewSDK::Resource
370
+ data.each do |k, v|
371
+ if v.class == Hash || v.class == Array
372
+ puts "#{' ' * indent}#{k}:"
373
+ output(v, indent + 2)
374
+ else
375
+ puts "#{' ' * indent}#{k}: #{v}"
376
+ end
377
+ end
378
+ elsif data.class == Array
379
+ data.each do |d|
380
+ if d.class == Hash || d.class == Array
381
+ # rubocop:disable Metrics/BlockNesting
382
+ if indent == 0
383
+ puts ''
384
+ output(d, indent)
385
+ else
386
+ output(d, indent + 2)
387
+ end
388
+ # rubocop:enable Metrics/BlockNesting
389
+ else
390
+ puts "#{' ' * indent}#{d}"
391
+ end
392
+ end
393
+ puts "\nTotal: #{data.size}" if indent == 0
394
+ else
395
+ puts "#{' ' * indent}#{data}"
396
+ end
397
+ end
398
+ end
399
+ end
400
+
401
+ # Console class
402
+ class Console
403
+ def initialize(client)
404
+ @client = client
405
+ end
406
+ end
407
+ end