purest 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: '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: []