Purest (or Pure Rest, if you will) -- a simple gem for interacting with Pure Storage's FlashArray REST API

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 FlashArray REST API.

Disclaimer

This started as a labor of love/learning exercise, and sort of blossomed into this. That being said, it means a few things:

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.

2) I am not an employee of Pure Storage, just a random nerd.

3) While all of the classes exist, currently only up to API version 1.16 is 'officially' supported- meaning it may work on newer versions, but I can't verify since I've only been able to develop against versions 1.16 and lower.

Table of contents

Requirements

To be captain obvious, this does require you have access to a Pure Storage FlashArray.

This library requires you use Ruby 2.3 or above.

Installation

gem install purest

Authentication

Purest allows you to authenticate using two different methods, either username and password OR using your API key (you don't need both). See "Configuration" below for examples. We recommend using the API key method as it is - slightly - more secure.

Configuration

There are now two ways to configure Purest, the first is directly in your ruby code like so:

require 'purest'

Purest.configure do |config|
  config.api_key = '1234-567-89'
  config.api_version = '1.16'
  config.options = {ssl: { verify: true }}
  config.url = "https://purehost.yourdomain.com"
end

or with the user credentials method:

require 'purest'

Purest.configure do |config|
  config.username = 'api-enabled-user'
  config.password = 'password'
  config.api_version = '1.16'
  config.options = {ssl: { verify: true }}
  config.url = "https://purehost.yourdomain.com"
end

The second method is to create a .purest.yaml file in your home directory (~/.purest.yaml) that looks like this:

---
api_key: '1234-567-89'
api_version: '1.16'
url: 'https://purehost.yourdomain.com'
options:
  ssl:
    verify: true

or with the user credentials method:

---
username: 'api-enabled-user'
password: 'password'
api_version: '1.16'
url: 'https://purehost.yourdomain.com'
options:
  ssl:
    verify: true

This yaml file is loaded in when your code is executed and the values can be individually or wholesale overridden during runtime by using the first configuration method, if you so desire.

API options

First: Authentication and session management are handled behind the scenes, you just need to supply your username/password or API key in the configuration block or YAML file (as shown in the example above). That's it.

Second: The various class methods of this gem turn the provided options into HTTP parameters, and are named accordingly. For instance, Purest::Volume.get({:snap: true}) translates to http://purehost.yourdomain.com/api/1.16/volume?snap=true. For a full list of options for a given class, Pure provides good documentation at: https://purehost.yourdomain.com/static/0/help/rest/.

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.

Usage

Alerts

Get information about alerts/alerting

# List email recipients
Purest::Alerts.get

# List information about a specific email recipient
Purest::Alerts.get(name: '[email protected]')

Designate an email address to receive alerts

Purest::Alerts.create(name: '[email protected]')

Updating/performing alert actions

# Send a test alert
Purest::Alerts.update(action: 'test')

# Turn off alert sending for [email protected]
Purest::Alerts.update(name: '[email protected]', enabled: false)

Deleting an email address from list of addresses that receive alerts

Purest::Alerts.delete(name: '[email protected]')

App

# Teeny tiny class, can get apps or get apps and initiators
Purest::App.get
Purest::App.get(initiators: true)

Cert

List certificate attributes or export a certificate

# Get certificate attributes
Purest::Cert.get

# Export current certificate
Purest::Cert.get(certificate: true)

# Export current intermediate certificate
Purest::Cert.get(intermediate_certificate: true)

# Get a CSR with current certificate attributes
Purest::Cert.get(csr: true)

# Get a CSR with current certificate attributes, but change the CN
Purest::Cert.get(csr: true, common_name: 'host.example.com')

Create self signed certificate, or import one. If you're wondering why this is an update action and not a create action, I believe it comes down to the fact that the Pure Array already has a self signed cert from the get go so we're just updating in place.

# Create a self signed certificate, using existing attributes
Purest::Cert.update(self_signed: true)

# Create a self signed cert, using all existing attributes except the state
Purest::Cert.update(self_signed: true, state: 'FL')

# Import a cert signed by a CA
Purest::Cert.update(certificate: 'your_huge_certificate_string')

Directory Service

Get information about directory service

Purest::DirectoryService.get

Update information about a directory service

Purest::DirectoryService.update(bind_password: 'superpassword')

DNS

Listing DNS attributes

Purest::DNS.get

Updating DNS attributes

Purest::DNS.update(nameservers: ['newdns1', 'newdns2'])

Drive information

List flash modules, NVRAM modules, and their attributes

Purest::Drive.get

Purest::Drive.get(name: 'SH0.BAY0')

Hardware

List hardware components

Purest::Hardware.get

# List a specific hardware component's attributes
Purest::Hardware.get(name: 'SH0.BAY0')

Control visual identification of specified components

# Turn the lights on
Purest::Hardware.update(name: 'SH0.BAY0', identify: on)

# Turn the lights off :(
Purest::Hardware.update(name: 'SH0.BAY0', identify: off)

Hosts

Getting hosts:

# Get a list of all the hosts on an array
Purest::Host.get

# Get a list of hosts with performance data
Purest::Host.get(action: 'monitor')

# Get a single host on an array
Purest::Host.get(name: 'host123')

# Get a list of hosts, by name, on an array
Purest::Host.get(names: ['host123', 'host456'])

# Get volumes associated with specific host
Purest::Host.get(name: 'hgroup1', show_volume: true)

Creating hosts:

# Create a host
Purest::Host.create(name: 'host123')

# Create a host and set ISCSI IQNs
Purest::Host.create(name: 'host123', iqnlist: ['iqnstuff-1', 'iqnstuff-2'])

# Add a host to a protection group
Purest::Host.create(name: 'host123', protection_group: 'pgroup123')

# Connect a volume to a host
Purest::Host.create(name: 'host123', volume: 'volume123')

Updating hosts:

# Rename a host
Purest::Host.update(name: 'host123', new_name: 'host456')

# Set the host username/password for CHAP
Purest::Host.update(name: 'host123', host_user: 'username', host_password: 'supersecretpassword')

Deleting hosts:

# Delete a host
Purest::Host.delete(name: 'host123')

# Remove a host from a protection group
Purest::Host.delete(name: 'host123', protection_group: 'pgroup123')

# Remove the connection between a host and a volume
Purest::Host.delete(name: 'host123', volume: 'volume123')

Host Groups

Getting information about host groups

# Get a list of host groups
Purest::HostGroup.get

# Get a single host group
Purest::HostGroup.get(name: 'hgroup1')

# Get a list of host groups, with monitoring information
Purest::HostGroup.get(names: ['hgroup1', 'hgroup2'], action: 'monitor')

# Get a list of volumes associated with a specified host
Purest::HostGroup.get(name: 'hgroup1', show_volume: true)

Creating host groups

# Create a host group with a specified name
Purest::HostGroup.create(name: 'hgroup1')

# Create a host group and supply its host members
Purest::HostGroup.create(name: 'hgroup1', hostlist: ['host1', 'host2'])

# Add a host group to a protection group
Purest::HostGroup.create(name: 'hgroup1', protection_group: 'pgroup1')

# Connect a volume to all hosts in a specified host group
Purest::HostGroup.create(name: 'hgroup1', volume: 'v3')

Updating host groups

# Renaming a host group
Purest::HostGroup.update(name: 'hgroup1', new_name: 'hgroup1-renamed')

# Replace the list of member hosts
Purest::HostGroup.update(name: 'hgroup1', hostlist: ['host1', 'host2'])

# Add a list of hosts to existing host list
Purest::HostGroup.update(name: 'hgroup1', addhostlist: ['host3', 'host4'])

# Remove a list of hosts from a host list
Purest::HostGroup.update(name: 'hgroup1', remhostlist: ['host1'])

Deleting host groups

# Delete a host group
Purest::HostGroup.delete(name: 'hgroup1')

# Remove a host group member from a protection group
Purest::HostGroup.delete(name: 'hgroup1', protection_group: 'pgroup1')

# Break the connection between a host group and a volume
Purest::HostGroup.delete(name: 'hgroup1', volume: 'volume1')

Messages

List alert events, audit records, etc

Purest::Messages.get

Flag/unflag a message

Purest::Messages.update(id: 2, flagged: true)

Network

List network interfaces

# Get network interfaces and their statuses
Purest::Network.get

# Get attributes about a specific network component
Purest::Network.get(name: 'ct0.eth0')

Create a VLAN interface

# Create a vlan with a specified subnet
Purest::Network.create(subnet: 'subnet10')

Perform network interface actions or update attributes

# Set MTU
Purest::Network.update(mtu: 2000)

Delete a VLAN interface

Purest::Network.delete(name: 'ct0.eth0')

Physical Arrays

List the attributes on an array:

# List all the attributes
Purest::PhysicalArray.get

# List connected arrays
Purest::PhysicalArray.get(connection: true)

Create a connection between two arrays:

Purest::PhysicalArray.create(connection_key: '<key>', management_address: 'hostname', type: ['replication'])

Update the attributes on an array:

# rename an array
Purest::PhysicalArray.update(new_name: 'new_name')

Disconnect the current array from a specified array:

# Given that your pure is purehost.yourdomain.com, as defined in the config block above
# Disconnect purehost2.yourdomain.com from purehost.yourdomain.com
Purest::PhysicalArray.delete(name: 'purehost2.yourdomain.com')

Pod

Getting attributes or displaying performance metrics for pods

# Get a list of pods and their details
Purest::Pod.get

# Show space information for each pod
Purest::Pod.get(space: true)

# Get a list of attributes for a specific pod
Purest::Pod.get(name: 'superpod')

Creating a new pod

# Basic pod creation
Purest::Pod.create(name: 'superpod2')

# Create a pod with a failover preference
Purest::Pod.create(name: 'superpod2', failover_preference: ['array1'])

Stretching a pod to a peer array

Purest::Pod.create(name: 'superpod2', array: 'array1')

Updating a pod

Purest::Pod.update(name: 'superpod2', failover_preference: ['array2'])

Unstretching a pod from a peer array

Purest::Pod.delete(name: 'superpod2', array: 'array1')

Deleting a pod

Purest::Pod.delete(name: 'superpod2')

# Eradicating a pod
Purest::Pod.delete(name: 'superpod2', eradicate: true)

Port

Getting information about ports, 'cause that's all you get to do

# Get port information
Purest::Port.get

# Get port information + initiator information = winning
Purest::Port.get(initiators: true)

Protection Groups

Getting information about protection groups

# Get a list of protection groups
Purest::ProtectionGroup.get

# Get a list of protection groups pending deletion
Purest::ProtectionGroup.get(pending: true)

Creating protection groups

# Create a protection group
Purest::ProtectionGroup.create(name: 'pgroup1')

# Create a protection group with a host list
Purest::ProtectionGroup.create(name: 'pgroup1', hostlist: ['host1', 'host2'])

Updating protection groups

# Renaming a protection group
Purest::ProtectionGroup.update(name: 'pgroup1', new_name: 'pgroup1-renamed')

# Add a list of hosts to an existing protection group's host list
Purest::ProtectionGroup.update(name: 'pgroup1', addhostlist: ['host3', 'host4'])

Deleting a protection group

# Delete it, but allow for it to remain during the 24 hour grace period
Purest::ProtectionGroup.delete(name: 'pgroup1')

# Delete it, with extreme prejudice
Purest::ProtectionGroup.delete(name: 'pgroup1', eradicate: true)

SNMP

Getting SNMP information

# Get a list of SNMP managers
Purest::SNMP.get

# List SNMP v3 engine ID
Purest::SNMP.get(engine_id: true)

Creating an SNMP manager

# Create an SNMP manager
Purest::SNMP.create(name: 'snmp-manager1', host: 'snmp.yourdomain.com')

# Create an SNMP manager pointed at an IPv4 address with a custom port
Purest::SNMP.create(name: 'snmp-manager1', host: '111.11.11.111:222')

# For those brave few; the strong souls using IPv6 with a custom port
Purest::SNMP.create(name: 'snmp-manager1', host: '[2001:db8:0:1]:222')

Updating an existing SNMP manager

# Renaming an SNMP manager
Purest::SNMP.update(name: 'snmp-manager1', new_name: 'snmp-manager-renamed')

# For those not so brave; the weak souls who give up using IPv6 with a custom port
Purest::SNMP.update(name: 'snmp-manager1', host: '111.11.11.111:222')

Deleting an SNMP manager

# Delete an SNMP manager
Purest::SNMP.delete(name: 'snmp-manager1')

Subnet

Getting information about subnets

Purest::Subnet.get

# Specify a subnet
Purest::Subnet.get(name: 'subnet10')

Creating a subnet

Purest::Subnet.create(name: 'subnet20', gateway: '11.11.11.1')

Updating a subnet

# Rename a subnet
Purest::Subnet.update(name: 'subnet20', new_name: 'subnet21')

Deleting a subnet

Purest::Subnet.delete(name: 'subnet21')

Users

Getting information about users

Purest::Users.get

# Specific user information
Purest::Users.get(name: 'paxton.fettle')

# List the API token of a given user
Purest::Users.get(name: 'paxton.fettle', api_token: true)

Create an API token for an existing user

Purest::Users.create(name: 'paxton.fettle')

Updating user related stuff

# Clear all user permission cache entries
Purest::Users.update(clear: true)

# Update a specific user's public key
Purest::Users.update(name: 'paxton.fettle', publickey: 'hugepublickeystring')

Delete API token for a user

Purest::Users.delete(name: 'paxton.fettle')

Volumes

Getting volumes:

# Get the full list of volumes
Purest::Volume.get

# Get a single volume
Purest::Volume.get(name: 'volume1')

# Get monitoring information about a volume
Purest::Volume.get(name: 'volume1', action: 'monitor')

# Get multiple volumes
Purest::Volume.get(names: ['volume1', 'volume2'])

# Get a list of snapshots
Purest::Volume.get(snap: true)

# Get a single snapshot
Purest::Volume.get(name: 'volume1', snap: true)

# Get multiple snapshots
Purest::Volume.get(names: ['volume1', 'volume2'], snap: true)

# List block differences for the specified snapshot
Purest::Volume.get(name: 'volume1.snap', show_diff: true, block_size: 512, length: '2G')

# List shared connections for a specified volume
Purest::Volume.get(name: 'volume1', show_hgroup: true)

# List private connections for a specified volume
Purest::Volume.get(:name: 'volume1', :show_host: true)

Creating volumes:

# Creating a new volume
Purest::Volume.create(name: 'volume', size: '10G')

# Creating a new volume by copying another
Purest::Volume.create(name: 'volume', source: 'other_vol')

# Overwriting a volume by copying another
Purest::Volume.create(name: 'volume', source: 'other_vol', overwrite: true)

# Add a volume to a protection group
Purest::Volume.create(name: 'volume', protection_group: 'protection-group')

Updating volumes

# Growing a volume
Purest::Volume.update(name: 'volume', size: "15G")

# Truncate (shrink) a volume
Purest::Volume.update(name: 'volume', size: "10G", truncate: true)

# Rename a volume
Purest::Volume.update(name: 'volume', new_name: 'volume_renamed')

Deleting volumes

# Delete a volume
Purest::Volume.delete(name: 'volume_to_delete')

# Eradicating a volume
Purest::Volume.delete(name: 'volume_to_delete', eradicate: true)

# Deleting a volume from a protection group
Purest::Volume.delete(name: 'volume', protection_group: 'pgroup1')

Specs

This library is tested with rspec, to execute the specs merely run

rspec

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.

Create an .integration.yaml inside the project, containing your pure array URL and credentials like so:

$ cat ~/ruby/purest/.integration.yaml
---
username: 'api-user'
password: 'api-password'
url: 'https://yoursuperawesomepurehost.com'

There are a few ways you can execute the integration tests:

# This will execute against the latest API version the gem is aware of, right now that is 1.16
rspec -t integration

# This will execute against a specific version
API_VERSION=1.10 rspec -t integration

# This will execute against all specified versions
API_VERSION=1.1,1.2,1.5 rspec -t integration

# And finally, if you wish to execute integration tests against every version
ALL_VERSIONS=true rspec -t integration

It is worth mentioning that this generates a fair bit of work for your Pure array so...you've been warned. All of that being said, the integration testing is somewhat sparse at the moment.

License

Purest is released under the MIT License.