purest 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: '0061492805748e646a06d9f5077446336d985ea40ff8ed76a3f9a5d8b60ff74d'
4
+ data.tar.gz: f58236ea14ff460b5698f0f348540c8c7fe87db762200fd37e8cb7ccfea3f505
5
+ SHA512:
6
+ metadata.gz: e51d88129e1f4807334c14ee3f9787e79bbcce33254dc83fa21450d63d67f1cdf3a687b84210df13b5eb5fad51ee5d30de71a2149e7e59672243036e287f0cea
7
+ data.tar.gz: c5f9532c491299d10b1c8e7add8bcefe1905534ca6496f84a5cf7ac5450726e2175d7fa6bdf8530e6da5cf761e61447273c623b8562c09313546a5817314dcac
@@ -0,0 +1,284 @@
1
+ # Purest (or Pure Rest, if you will) -- a simple gem for interacting with Pure Storage's REST API
2
+
3
+ A simple to use library for Ruby, inspired by the WeAreFarmGeek's Diplomat gem (seriously, those guys are awesome), allowing for easy interaction with Pure Storage's REST API.
4
+
5
+ ## Disclaimer
6
+ This started as sort of a labor of love/learning exercise, and sort of blossomed into this. That being said, it means a few things:
7
+
8
+ 1) I may have made some stupid mistakes in here, if so..so be it. Raise them in issues or submit PRs, and I'll gladly fix/merge if I feel the code submitted carries the spirit of my little project. Odds are I won't reject a PR unless you try to rewrite everything for some obtuse reason I don't agree with.
9
+
10
+ 2) I am not affiliated with Pure Storage, beyond the fact that my company uses their product.
11
+
12
+ 3) This isn't done. What I think of as the 'core' functionality is there- you can manipulate volumes, hosts, and host groups, along with the array itself. I still need to add in classes for ProtectionGroups, Alerts/Messages, SNMP Manager Connections, SSL, Network Interfaces, Hardware, Apps, and Users.
13
+
14
+ ## Requirements
15
+
16
+ To be captain obvious, this does require you have access to a Pure Storage array.
17
+
18
+ This library requires you use Ruby 2.3 or above.
19
+
20
+ # Usage
21
+
22
+ ## Configuration
23
+
24
+ ```
25
+ require 'purest'
26
+
27
+ Purest.configure do |config|
28
+ config.url = "https://purehost.yourdomain.com"
29
+ config.options = {ssl: { verify: true }}
30
+ config.api_version = '1.11'
31
+ config.username = 'api-enabled-user'
32
+ config.password = 'password'
33
+ end
34
+ ```
35
+
36
+ ## API options
37
+ The various class methods of this gem turn the provided options into HTTP parameters, and are
38
+ named accordingly. For instance, ```Purest::Volume.get({:snap => true})``` translates
39
+ to http://purehost.yourdomain.com/api/1.11/volume?snap=true. For a full list
40
+ of options for a given class, Pure provides good documentation at:
41
+ https://purehost.yourdomain.com/static/0/help/rest/.
42
+
43
+ Below I'll provide a large group of examples, but I won't be detailing every single method call with all of its possible options, for that I will again refer you to Pure's REST API docs.
44
+
45
+ # Examples
46
+ ## Volumes
47
+ Getting volumes:
48
+ ```ruby
49
+ # Get the full list of volumes
50
+ Purest::Volume.get
51
+
52
+ # Get a single volume
53
+ Purest::Volume.get(:name => 'volume1')
54
+
55
+ # Get monitoring information about a volume
56
+ Purest::Volume.get(:name => 'volume1', :action => 'monitor')
57
+
58
+ # Get multiple volumes
59
+ Purest::Volume.get(:names => ['volume1', 'volume2'])
60
+
61
+ # Get a list of snapshots
62
+ Purest::Volume.get(:snap => true)
63
+
64
+ # Get a single snapshot
65
+ Purest::Volume.get(:name => 'volume1', :snap => true)
66
+
67
+ # Get multiple snapshots
68
+ Purest::Volume.get(:names => ['volume1', 'volume2'], :snap => true)
69
+
70
+ # List block differences for the specified snapshot
71
+ Purest::Volume.get(:name => 'volume1.snap', :show_diff => true, :block_size => 512, :length => '2G')
72
+
73
+ # List shared connections for a specified volume
74
+ Purest::Volume.get(:name => 'volume1', :show_hgroup => true)
75
+
76
+ # List private connections for a specified volume
77
+ Purest::Volume.get(:name => 'volume1', :show_host => true)
78
+ ```
79
+
80
+ Creating volumes:
81
+
82
+ ```ruby
83
+ # Creating a new volume
84
+ Purest::Volume.create(:name => 'volume', :size => '10G')
85
+
86
+ # Creating a new volume by copying another
87
+ Purest::Volume.create(:name => 'volume, ':source => 'other_vol')
88
+
89
+ # Overwriting a volume by copying another
90
+ Purest::Volume.create(:name => 'volume', :source => 'other_vol', :overwrite => true)
91
+
92
+ # Add a volume to a protection group
93
+ Purest::Volume.create(:name => 'volume', :pgroup => 'protection-group')
94
+ ```
95
+
96
+ Updating volumes
97
+ ```ruby
98
+ # Growing a volume
99
+ Purest::Volume.update(:name => 'volume', :size => "15G")
100
+
101
+ # Truncate (shrink) a volume
102
+ Purest::Volume.update(:name => 'volume', :size => "10G", :truncate => true)
103
+
104
+ # Rename a volume
105
+ Purest::Volume.update(:name => 'volume', :new_name => 'volume_renamed')
106
+ ```
107
+
108
+ Deleting volumes
109
+
110
+ ```ruby
111
+ # Delete a volume
112
+ Purest::Volume.delete(:name => 'volume_to_delete')
113
+
114
+ # Eradicating a volume
115
+ Purest::Volume.delete(:name => 'volume_to_delete', :eradicate => true)
116
+
117
+ # Deleting a volume from a protection group
118
+ Purest::Volume.delete(:name => 'volume', :pgroup => 'pgroup1')
119
+ ```
120
+
121
+ ## Hosts
122
+ Getting hosts:
123
+ ```ruby
124
+ # Get a list of all the hosts on an array
125
+ Purest::Host.get
126
+
127
+ # Get a list of hosts with performance data
128
+ Purest::Host.get(:action => 'monitor')
129
+
130
+ # Get a single host on an array
131
+ Purest::Host.get(:name => 'host123')
132
+
133
+ # Get a list of hosts, by name, on an array
134
+ Purest::Host.get(:names => ['host123', 'host456'])
135
+ ```
136
+
137
+ Creating hosts:
138
+ ```ruby
139
+ # Create a host
140
+ Purest::Host.create({:name => 'host123'})
141
+
142
+ # Create a host and set ISCSI IQNs
143
+ Purest::Host.create(:name => 'host123', :iqnlist => ['iqnstuff-1', 'iqnstuff-2'])
144
+
145
+ # Add a host to a protection group
146
+ Purest::Host.create(:name => 'host123', :protection_group => 'pgroup123')
147
+
148
+ # Connect a volume to a host
149
+ Purest::Host.create(:name => 'host123', :volume => 'volume123')
150
+ ```
151
+
152
+ Updating hosts:
153
+ ```ruby
154
+ # Rename a host
155
+ Purest::Host.update(:name => 'host123', :new_name => 'host456')
156
+
157
+ # Set the host username/password for CHAP
158
+ Purest::Host.update(:name => 'host123', :host_user => 'username', :host_password => 'supersecretpassword')
159
+ ```
160
+
161
+ Deleting hosts:
162
+ ```ruby
163
+ # Delete a host
164
+ Purest::Host.delete(:name => 'host123')
165
+
166
+ # Remove a host from a protection group
167
+ Purest::Host.delete(:name => 'host123', :protection_group => 'pgroup123')
168
+
169
+ # Remove the connection between a host and a volume
170
+ Purest::Host.delete(:name => 'host123', :volume => 'volume123')
171
+ ```
172
+
173
+ # Physical Arrays
174
+
175
+ List the attributes on an array:
176
+ ```ruby
177
+ # List all the attributes
178
+ Purest::PhysicalArray.get
179
+
180
+ # List connected arrays
181
+ Purest::PhysicalArray.get(:connection => true)
182
+ ```
183
+
184
+ Create a connection between two arrays:
185
+ ```ruby
186
+ Purest::PhysicalArray.create(:connection_key => '<key>', :management_address => 'hostname', :type => ['replication'])
187
+ ```
188
+
189
+ Update the attributes on an array:
190
+ ```ruby
191
+ # rename an array
192
+ Purest::PhysicalArray.update(:new_name => 'new_name')
193
+ ```
194
+
195
+ Disconnect the current array from a specified array:
196
+ ```ruby
197
+ # Given that your pure is purehost.yourdomain.com, as defined in the config block above
198
+ # Disconnect purehost2.yourdomain.com from yours
199
+ Purest::PhysicalArray.delete(:name => 'purehost2.yourdomain.com')
200
+ ```
201
+
202
+ # Host Groups
203
+ Getting information about host groups
204
+ ```ruby
205
+ # Get a list of host groups
206
+ Purest::HostGroup.get
207
+
208
+ # Get a single host group
209
+ Purest::HostGroup.get(:name => 'hgroup1')
210
+
211
+ # Get a list of host groups, with monitoring information
212
+ Purest::HostGroup.get(:names => ['hgroup1', 'hgroup2'], :action => 'monitor')
213
+
214
+ # Get a list of volumes associated with a specified host
215
+ Purest::HostGroup.get(:name => 'hgroup1', :show_volumes => true)
216
+ ```
217
+
218
+ Creating host groups
219
+ ```ruby
220
+ # Create a host group with a specified name
221
+ Purest::HostGroup.create(:name => 'hgroup1')
222
+
223
+ # Create a host group and supply its host members
224
+ Purest::HostGroup.create(:name => 'hgroup1', :hostlist => ['host1', 'host2'])
225
+
226
+ # Add a host group to a protection group
227
+ Purest::HostGroup.create(:name => 'hgroup1', :protection_group => 'pgroup1')
228
+
229
+ # Connect a volume to all hosts in a specified host group
230
+ Purest::HostGroup.create(:name => 'hgroup1', :volume => 'v3')
231
+ ```
232
+
233
+ Updating host groups
234
+ ```ruby
235
+ # Renaming a host group
236
+ Purest::HostGroup.update(:name => 'hgroup1', :new_name => 'hgroup1-renamed')
237
+
238
+ # Replace the list of member hosts
239
+ Purest::HostGroup.update(:name => 'hgroup1', :hostlist => ['host1', 'host2'])
240
+
241
+ # Add a list of hosts to existing host list
242
+ Purest::HostGroup.update(:name => 'hgroup1', :addhostlist => ['host3', 'host4'])
243
+
244
+ # Remove a list of hosts from a host list
245
+ Purest::HostGroup.update(:name => 'hgroup1', :remhostlist => ['host1'])
246
+ ```
247
+
248
+ Deleting host groups
249
+ ```ruby
250
+ # Delete a host group
251
+ Purest::HostGroup.delete(:name => 'hgroup1')
252
+
253
+ # Remove a host group member from a protection group
254
+ Purest::HostGroup.delete(:name => 'hgroup1', :protection_group => 'pgroup1')
255
+
256
+ # Break the connection between a host group and a volume
257
+ Purest::HostGroup.delete(:name => 'hgroup1', :volume => 'volume1')
258
+ ```
259
+
260
+ # Specs
261
+ This library is tested with rspec, to execute the specs merely run
262
+ ```
263
+ rspec
264
+ ```
265
+
266
+ This project also supports an integration test suite. However, you must have API access to a pure array for this integration test suite to work.
267
+
268
+ Create an .integration.yaml inside the project, containing your pure array URL and credentials like so:
269
+ ```
270
+ $ cat ~/ruby/purest/.integration.yaml
271
+ ---
272
+ username: 'api-user'
273
+ password: 'api-password'
274
+ url: 'https://yoursuperawesomepurehost.com'
275
+ ```
276
+
277
+ Execute integration tests:
278
+ ```
279
+ rspec -t integration
280
+ ```
281
+
282
+ By default, this will run against version 1.1 to 1.11 of the API. This is useful for ensuring functionality added/subtracted to this project is programmatically tested against all versions of the API. I mostly did this as an exercise, that being said I think it provides a lot of usefulness and if it can be improved let me know (or submit a PR).
283
+
284
+ It is worth mentioning, this generates a fair bit of work for your Pure array so...you've been warned.
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+ require 'json'
5
+ require 'faraday'
6
+ require 'faraday_middleware'
7
+ require 'faraday-cookie_jar'
8
+ require 'pry'
9
+ require 'psych'
10
+
11
+ module Purest
12
+ class << self
13
+ attr_accessor :root_path
14
+ attr_accessor :lib_path
15
+ attr_accessor :configuration
16
+
17
+ # Internal: Requires internal Faraday libraries.
18
+ # @param *libs One or more relative String names to Faraday classes.
19
+ # @return [nil]
20
+ def require_libs(*libs)
21
+ libs.each do |lib|
22
+ require "#{lib_path}/#{lib}"
23
+ end
24
+ end
25
+
26
+ alias require_lib require_libs
27
+ end
28
+
29
+ raise 'Purest only supports ruby >= 2.3.0' unless RUBY_VERSION.to_f >= 2.3
30
+
31
+ self.root_path = File.expand_path __dir__
32
+ self.lib_path = File.expand_path 'purest', __dir__
33
+
34
+ require_libs 'custom_exceptions', 'configuration', 'rest', 'host', 'host_group', 'volume', 'physical_array'
35
+
36
+ self.configuration ||= Purest::Configuration.new
37
+
38
+ class << self
39
+ # Build optional configuration by yielding a block to configure
40
+ # @yield [Purest::Configuration]
41
+ def configure
42
+ self.configuration ||= Purest::Configuration.new
43
+ yield(configuration)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Purest
4
+ # Methods for configuring Purest
5
+ class Configuration
6
+ attr_accessor :api_version, :url, :username, :password, :options
7
+
8
+ # Override defaults for configuration
9
+ # @param api_version [String] the API version to interact with
10
+ # @param url [String] pure's connection URL
11
+ # @param username [String] username to authenticate with
12
+ # @param password [String] password to authenticate with
13
+ # @param options [Hash] extra options to configure Faraday::Connection
14
+ def initialize(api_version = '1.11', url = nil, username = nil, password = nil, options = {})
15
+ @api_version = api_version
16
+ @url = url
17
+ @username = username
18
+ @password = password
19
+ @options = options
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Purest
4
+ class CustomExceptions
5
+ class ConflictingArguments < StandardError
6
+ end
7
+
8
+ class RequiredArgument < StandardError
9
+ end
10
+
11
+ class ArgumentMismatch < StandardError
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Purest
4
+ class Host < Purest::Rest
5
+ @access_methods = %i[get create update delete]
6
+
7
+ GET_PARAMS = [:action, :all, :chap, :connect, :names, :personality,
8
+ :private, :protect, :shared, :space]
9
+
10
+ # Get a list of hosts, GET
11
+ # @param options [Hash] options to pass
12
+ def get(options = nil)
13
+ @options = options
14
+ create_session unless authenticated?
15
+
16
+ raw_resp = @conn.get do |req|
17
+ url = ["/api/#{Purest.configuration.api_version}/host"]
18
+ url.map!{|u| u + "/#{@options[:name]}"} if !@options.nil? && @options[:name]
19
+ url.map!{|u| u + "/volume"} if !@options.nil? && @options[:show_volumes]
20
+
21
+ # Generate array, consisting of url parts, to be built
22
+ # by concat_url method below
23
+ GET_PARAMS.each do |param|
24
+ url += self.send(:"use_#{param}",@options)
25
+ end
26
+
27
+ # Build url from array of parts, send request
28
+ req.url concat_url url
29
+ end
30
+
31
+ JSON.parse(raw_resp.body, :symbolize_names => true)
32
+ end
33
+
34
+ # Create a host, POST
35
+ # @param options [Hash] options to pass
36
+ def create(options = nil)
37
+ @options = options
38
+
39
+ raise RequiredArgument, ':name required when creating a host' if @options.nil? || @options[:name].nil?
40
+ create_session unless authenticated?
41
+
42
+ raw_resp = @conn.post do |req|
43
+ url = ["/api/#{Purest.configuration.api_version}/host/#{@options[:name]}"]
44
+ url.map!{|u| u + "/pgroup/#{@options[:protection_group]}"} if @options[:protection_group]
45
+ url.map!{|u| u + "/volume/#{@options[:volume]}"} if @options[:volume]
46
+ req.body = @options.to_json
47
+
48
+ req.url concat_url url
49
+ end
50
+
51
+ JSON.parse(raw_resp.body, :symbolize_names => true)
52
+ end
53
+
54
+ # Update a host, PUT
55
+ # @param options [Hash] options to pass
56
+ def update(options = nil)
57
+ @options = options
58
+
59
+ create_session unless authenticated?
60
+
61
+ raw_resp = @conn.put do |req|
62
+ req.url "/api/#{Purest.configuration.api_version}/host/#{@options[:name]}"
63
+
64
+ # Repurpose @options[:name] so that it is now set to the new_name
65
+ # allowing us to rename a volume.
66
+ @options[:name] = @options.delete(:new_name) if @options[:new_name]
67
+ req.body = @options.to_json
68
+ end
69
+
70
+ JSON.parse(raw_resp.body, :symbolize_names => true)
71
+ end
72
+
73
+ def delete(options = nil)
74
+ @options = options
75
+
76
+ create_session unless authenticated?
77
+
78
+ raw_resp = @conn.delete do |req|
79
+ url = ["/api/#{Purest.configuration.api_version}/host/#{@options[:name]}"]
80
+ url.map!{|u| u + "/pgroup/#{@options[:protection_group]}"} if @options[:protection_group]
81
+ url.map!{|u| u + "/volume/#{@options[:volume]}"} if @options[:volume]
82
+
83
+ req.url concat_url url
84
+ end
85
+
86
+ JSON.parse(raw_resp.body, :symbolize_names => true)
87
+ end
88
+
89
+ private
90
+
91
+ # Dynamic method generation, e.g. use_all, use_action, etc
92
+ GET_PARAMS.each do |param|
93
+ define_method :"use_#{param}" do |options|
94
+ options ? use_named_parameter(param, options[param]) : []
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ module Purest
4
+ class HostGroup < Purest::Rest
5
+ @access_methods = %i[get create update delete]
6
+
7
+ GET_PARAMS = [:action, :block_size, :connect, :historical, :length, :names,
8
+ :pending, :pending_only, :pgrouplist, :private, :protect,
9
+ :shared, :snap, :space]
10
+
11
+ # Get a list of hostgroups, GET
12
+ # @param options [Hash] options to pass in
13
+ def get(options = nil)
14
+ @options = options
15
+ create_session unless authenticated?
16
+
17
+ raw_resp = @conn.get do |req|
18
+ url = ["/api/#{Purest.configuration.api_version}/hgroup"]
19
+ url.map!{|u| u + "/#{@options[:name]}"} if !@options.nil? && @options[:name]
20
+ url.map!{|u| u + "/volume"} if !@options.nil? && @options[:name] && @options[:show_volumes]
21
+
22
+ # Generate array, consisting of url parts, to be built
23
+ # by concat_url method below
24
+ GET_PARAMS.each do |param|
25
+ url += self.send(:"use_#{param}",@options)
26
+ end
27
+
28
+ req.url concat_url url
29
+ end
30
+
31
+ JSON.parse(raw_resp.body, :symbolize_names => true)
32
+ end
33
+
34
+ def create(options = nil)
35
+ @options = options
36
+ create_session unless authenticated?
37
+
38
+ raw_resp = @conn.post do |req|
39
+ url = ["/api/#{Purest.configuration.api_version}/hgroup/#{@options[:name]}"]
40
+ url.map!{|u| u + "/pgroup/#{@options[:protection_group]}"} if @options[:protection_group]
41
+ url.map!{|u| u + "/volume/#{@options[:volume]}"} if @options[:volume]
42
+
43
+ req.body = @options.to_json
44
+ req.url concat_url url
45
+ end
46
+
47
+ JSON.parse(raw_resp.body, :symbolize_names => true)
48
+ end
49
+
50
+ # Update a host group, PUT
51
+ # @param options [Hash] options to pass
52
+ def update(options = nil)
53
+ @options = options
54
+
55
+ create_session unless authenticated?
56
+
57
+ raw_resp = @conn.put do |req|
58
+ req.url "/api/#{Purest.configuration.api_version}/hgroup/#{@options[:name]}"
59
+
60
+ # Repurpose @options[:name] so that it is now set to the new_name
61
+ # allowing us to rename a volume.
62
+ @options[:name] = @options.delete(:new_name) if @options[:new_name]
63
+ req.body = @options.to_json
64
+ end
65
+
66
+ JSON.parse(raw_resp.body, :symbolize_names => true)
67
+ end
68
+
69
+ # Delete a host group, DELETE
70
+ # @param options[Hash] options to pass
71
+ def delete(options = nil)
72
+ @options = options
73
+
74
+ create_session unless authenticated?
75
+
76
+ raw_resp = @conn.delete do |req|
77
+ url = ["/api/#{Purest.configuration.api_version}/hgroup/#{@options[:name]}"]
78
+ url.map!{|u| u + "/pgroup/#{@options[:protection_group]}"} if @options[:protection_group]
79
+ url.map!{|u| u + "/volume/#{@options[:volume]}"} if @options[:volume]
80
+
81
+ req.url concat_url url
82
+ end
83
+
84
+ JSON.parse(raw_resp.body, :symbolize_names => true)
85
+ end
86
+
87
+ private
88
+ # Dynamic method generation, e.g. use_all, use_action, etc
89
+ GET_PARAMS.each do |param|
90
+ define_method :"use_#{param}" do |options|
91
+ options ? use_named_parameter(param, options[param]) : []
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Purest
4
+ class PhysicalArray < Purest::Rest
5
+ @access_methods = %i[get create update delete]
6
+
7
+ GET_PARAMS = [:action, :banner, :connection_key, :controllers, :historical,
8
+ :idle_timeout, :ntpserver, :phonehome, :proxy, :relayhost, :scsi_timeout,
9
+ :senderdomain, :space, :syslogserver, :throttle]
10
+
11
+ # Get a list of hosts, GET
12
+ # @param options [Hash] options to pass
13
+ def get(options = nil)
14
+ @options = options
15
+ create_session unless authenticated?
16
+
17
+ raw_resp = @conn.get do |req|
18
+ url = ["/api/#{Purest.configuration.api_version}/array"]
19
+
20
+ # Map /connection, /console_lock, /phonehome, /remoteassist
21
+ # depending on what was passed in
22
+ [:connection, :console_lock, :phonehome, :remoteassist].each do |path|
23
+ url.map!{|u| u + "/#{path.to_s}"} if !@options.nil? && @options[path]
24
+ end
25
+
26
+ # Generate array, consisting of url parts, to be built
27
+ # by concat_url method below
28
+ GET_PARAMS.each do |param|
29
+ url += self.send(:"use_#{param}",@options)
30
+ end
31
+
32
+ # Build url from array of parts, send request
33
+ req.url concat_url url
34
+ end
35
+
36
+ JSON.parse(raw_resp.body, :symbolize_names => true)
37
+ end
38
+
39
+ # Create a connection between two arrays
40
+ # @param options [Hash] options to pass
41
+ def create(options = nil)
42
+ @options = options
43
+
44
+ raw_resp = @conn.post do |req|
45
+ req.url "/api/#{Purest.configuration.api_version}/array/connection"
46
+ req.body = @options.to_json
47
+ end
48
+
49
+ JSON.parse(raw_resp.body, :symbolize_names => true)
50
+ end
51
+
52
+ # Update attributes on an array, PUT
53
+ # @param options [Hash] options to pass in
54
+ def update(options = nil)
55
+ @options = options
56
+
57
+ raw_resp = @conn.put do |req|
58
+ url = ["/api/#{Purest.configuration.api_version}/array"]
59
+
60
+ if !@options.nil? && @options[:connected_array]
61
+ url.map!{|u| u + "/connection/#{@options[:connected_array]}"}
62
+ end
63
+
64
+ # Small conditional to ease remote assistance connecting/disconnecting
65
+ if !@options.nil? && @options[:remote_assist]
66
+ url.map!{|u| u + "/remoteassist"}
67
+ @options[:action] = @options.delete(:remote_assist)
68
+ end
69
+
70
+ # Small loop to ease console locking and home phoning
71
+ [:console_lock, :phonehome].each do |path|
72
+ if !@options.nil? && @options[path]
73
+ url.map!{|u| u + "/#{path.to_s}"}
74
+ @options[:enabled] = @options.delete(path)
75
+ end
76
+ end
77
+
78
+ # Keeping things consistent
79
+ @options[:name] = @options.delete(:new_name) if @options[:new_name]
80
+
81
+ # Don't send this as part of the JSON body if it's present
82
+ # in the options hash, as it's not a valid param
83
+ @options.delete(:connected_array)
84
+ req.body = @options.to_json
85
+
86
+ req.url concat_url url
87
+ end
88
+
89
+ JSON.parse(raw_resp.body, :symbolize_names => true)
90
+ end
91
+
92
+ # Disconnect one array from another
93
+ # @param options [Hash] options to pass in
94
+ def delete(options = nil)
95
+ @options = options
96
+
97
+ raw_resp = @conn.delete do |req|
98
+ req.url "/api/#{Purest.configuration.api_version}/array/connection/#{@options[:connected_array]}"
99
+ end
100
+ end
101
+
102
+ private
103
+
104
+ GET_PARAMS.each do |attribute|
105
+ define_method :"use_#{attribute}" do |options|
106
+ options ? use_named_parameter(attribute, options[attribute]) : []
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Purest
4
+ # Base class for interacting with PURE storage REST API
5
+ class Rest < Purest::CustomExceptions
6
+ @access_methods = []
7
+ @get_params = []
8
+
9
+ # Initialize the fadaray connection, create session unless one exists
10
+ def initialize
11
+ establish_connection
12
+ create_session unless authenticated?
13
+ end
14
+
15
+ # Check if session exists, and whether or not it's expired
16
+ def authenticated?
17
+ if defined? @session_expire
18
+ if Time.now.utc < @session_expire
19
+ true
20
+ else
21
+ false
22
+ end
23
+ else
24
+ false
25
+ end
26
+ end
27
+
28
+ # Assemble a url from an array of parts.
29
+ # @param parts [Array] the url chunks to be assembled
30
+ # @return [String] the resultant url string
31
+ def concat_url(parts)
32
+ if parts.length > 1
33
+ parts.first + '?' + parts.drop(1).join('&')
34
+ else
35
+ parts.first
36
+ end
37
+ end
38
+
39
+ # Format url parameters into strings correctly
40
+ # @param name [String] the name of the parameter
41
+ # @param value [String] the value of the parameter
42
+ # @return [Array] the resultant parameter string inside an array.
43
+ def use_named_parameter(name, value)
44
+ if value.is_a? Array
45
+ ["#{name}=#{value.join(',')}"]
46
+ else
47
+ value ? ["#{name}=#{value}"] : []
48
+ end
49
+ end
50
+
51
+ # Logout current session
52
+ def logout
53
+ raw_resp = @conn.delete do |req|
54
+ req.url "/api/#{Purest.configuration.api_version}/auth/session"
55
+ end
56
+ remove_instance_variable(:@session_expire)
57
+ end
58
+
59
+ class << self
60
+ def access_method?(meth_id)
61
+ @access_methods.include? meth_id
62
+ end
63
+
64
+ def method_missing(meth_id, *args)
65
+ if access_method?(meth_id)
66
+ new.send(meth_id, *args)
67
+ else
68
+
69
+ # See https://bugs.ruby-lang.org/issues/10969
70
+ begin
71
+ super
72
+ rescue NameError => err
73
+ raise NoMethodError, err
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ def get_token
82
+ raw_resp = @conn.post do |req|
83
+ req.url "/api/#{Purest.configuration.api_version}/auth/apitoken"
84
+ req.params = {
85
+ "username": Purest.configuration.username.to_s,
86
+ "password": Purest.configuration.password.to_s
87
+ }
88
+ end
89
+
90
+ JSON.parse(raw_resp.body)['api_token']
91
+ end
92
+
93
+ def create_session
94
+ raw_resp = @conn.post do |req|
95
+ req.url "/api/#{Purest.configuration.api_version}/auth/session"
96
+ req.params = {
97
+ "api_token": get_token
98
+ }
99
+ end
100
+
101
+ # Man they really tuck that away, don't they?
102
+ @session_expire = Time.parse(raw_resp.env.response_headers['set-cookie'].split(';')[1].split(',')[1].strip).utc
103
+
104
+ raw_resp
105
+ end
106
+
107
+ # Build the API Client
108
+ def establish_connection
109
+ @conn = build_connection
110
+ end
111
+
112
+ def build_connection
113
+ Faraday.new(Purest.configuration.url, Purest.configuration.options) do |faraday|
114
+ faraday.request :json
115
+ faraday.use :cookie_jar
116
+ faraday.adapter Faraday.default_adapter
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Purest
4
+ class Volume < Purest::Rest
5
+ @access_methods = %i[get create update delete]
6
+
7
+ GET_PARAMS = [:action, :block_size, :connect, :historical, :length, :names,
8
+ :pending, :pending_only, :pgrouplist, :private, :protect,
9
+ :shared, :snap, :space]
10
+
11
+ # Get a list of volumes, GET
12
+ # @param options [Hash] options to pass
13
+ def get(options = nil)
14
+ @options = options
15
+ create_session unless authenticated?
16
+
17
+ # Build up a url from parts, allowing for dynamic http calls
18
+ # by simply passing in params accepted by Pure's API
19
+ raw_resp = @conn.get do |req|
20
+ url = ["/api/#{Purest.configuration.api_version}/volume"]
21
+
22
+ url.map!{|u| u + "/#{@options[:name]}"} if !@options.nil? && @options[:name]
23
+
24
+ # Map /diff, /hgroup, or /host depending on option passed
25
+ [:show_diff, :show_hgroup, :show_host].each do |path|
26
+ new_path = path.to_s.gsub('show_', '')
27
+ url.map!{|u| u + "/#{new_path}"} if !@options.nil? && @options[path]
28
+ end
29
+
30
+ # Generate array, consisting of url parts, to be built
31
+ # by concat_url method below
32
+ GET_PARAMS.each do |param|
33
+ url += self.send(:"use_#{param}",@options)
34
+ end
35
+
36
+ # Build url from array of parts, send request
37
+ req.url concat_url url
38
+ end
39
+
40
+ JSON.parse(raw_resp.body, :symbolize_names => true)
41
+ end
42
+
43
+ # Create a volume, POST
44
+ # @param options [Hash] options to pass
45
+ def create(options = nil)
46
+ @options = options
47
+
48
+ raw_resp = @conn.post do |req|
49
+ url = "/api/#{Purest.configuration.api_version}/volume"
50
+ url += "/#{@options[:name]}" if @options[:name]
51
+ url += "/pgroup/#{@options[:pgroup]}" if @options[:pgroup]
52
+ req.body = @options.to_json
53
+
54
+ req.url url
55
+ end
56
+
57
+ JSON.parse(raw_resp.body, :symbolize_names => true)
58
+ end
59
+
60
+ # Update a volume, PUT
61
+ # @param options [Hash] options to pass
62
+ def update(options = nil)
63
+ @options = options
64
+
65
+ raw_resp = @conn.put do |req|
66
+ # Here we construct the url first, because the options hash
67
+ # may have to be manipulated below
68
+ req.url "/api/#{Purest.configuration.api_version}/volume/#{@options[:name]}"
69
+
70
+ # Repurpose @options[:name] so that it is now set to the new_name
71
+ # allowing us to rename a volume.
72
+ @options[:name] = @options.delete(:new_name) if @options[:new_name]
73
+
74
+ # Remove name from @options which is sent in the body,
75
+ # if size is passed in, because those two cannot be sent together.
76
+ @options.delete(:name) if @options[:size]
77
+
78
+ req.body = @options.to_json
79
+ end
80
+
81
+ JSON.parse(raw_resp.body, :symbolize_names => true)
82
+ end
83
+
84
+ # Delete a volume, DELETE
85
+ # @param options [Hash] options to pass
86
+ def delete(options = nil)
87
+ @options = options
88
+
89
+ raw_resp = @conn.delete do |req|
90
+ url = "/api/#{Purest.configuration.api_version}/volume/#{@options[:name]}"
91
+ url += "/pgroup/#{@options[:pgroup]}" if @options[:pgroup]
92
+
93
+ req.url url
94
+ end
95
+
96
+ if @options[:eradicate]
97
+ raw_resp = @conn.delete do |req|
98
+ url = ["/api/#{Purest.configuration.api_version}/volume/#{@options[:name]}"]
99
+ req.body = @options.to_json
100
+
101
+ req.url concat_url url
102
+ end
103
+ end
104
+
105
+ JSON.parse(raw_resp.body, :symbolize_names => true)
106
+ end
107
+
108
+ private
109
+
110
+ # Dynamic method generation, e.g. use_connect, use_eradicate, etc
111
+ GET_PARAMS.each do |param|
112
+ define_method :"use_#{param}" do |options|
113
+ options ? use_named_parameter(param, options[param]) : []
114
+ end
115
+ end
116
+ end
117
+ end
metadata ADDED
@@ -0,0 +1,178 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: purest
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sean McKinley
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-06-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: cucumber
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.11.3
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.11.3
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 3.7.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 3.7.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: fakes-rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.1'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.57.2
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.57.2
83
+ - !ruby/object:Gem::Dependency
84
+ name: webmock
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 3.4.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 3.4.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: faraday
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.15.2
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.15.2
111
+ - !ruby/object:Gem::Dependency
112
+ name: faraday_middleware
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 0.12.2
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 0.12.2
125
+ - !ruby/object:Gem::Dependency
126
+ name: faraday-cookie_jar
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 0.0.6
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 0.0.6
139
+ description:
140
+ email: sean.mckinley@outlook.com
141
+ executables: []
142
+ extensions: []
143
+ extra_rdoc_files: []
144
+ files:
145
+ - README.md
146
+ - lib/purest.rb
147
+ - lib/purest/configuration.rb
148
+ - lib/purest/custom_exceptions.rb
149
+ - lib/purest/host.rb
150
+ - lib/purest/host_group.rb
151
+ - lib/purest/physical_array.rb
152
+ - lib/purest/rest.rb
153
+ - lib/purest/volume.rb
154
+ homepage:
155
+ licenses:
156
+ - MIT
157
+ metadata: {}
158
+ post_install_message:
159
+ rdoc_options: []
160
+ require_paths:
161
+ - lib
162
+ required_ruby_version: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ required_rubygems_version: !ruby/object:Gem::Requirement
168
+ requirements:
169
+ - - ">="
170
+ - !ruby/object:Gem::Version
171
+ version: '0'
172
+ requirements: []
173
+ rubyforge_project:
174
+ rubygems_version: 2.7.6
175
+ signing_key:
176
+ specification_version: 4
177
+ summary: A ruby gem for interacting with PURE storage's REST API
178
+ test_files: []