oneview-sdk 1.0.0

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