voxel-hapi 1.1.6
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.
- data/bin/rhapi +173 -0
- data/examples/hapirc.example +7 -0
- data/lib/hapi.rb +216 -0
- metadata +70 -0
data/bin/rhapi
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
|
4
|
+
|
5
|
+
require "rubygems"
|
6
|
+
require 'optparse'
|
7
|
+
require 'yaml'
|
8
|
+
gem "hapi", ">= 1.1.0"
|
9
|
+
require "hapi"
|
10
|
+
require "pp"
|
11
|
+
|
12
|
+
option_data = [
|
13
|
+
{
|
14
|
+
:short => '-U', :long => '--username USERNAME',
|
15
|
+
:type => String, :key => :username,
|
16
|
+
:description => 'hAPI Username',
|
17
|
+
:required => true
|
18
|
+
},
|
19
|
+
|
20
|
+
{
|
21
|
+
:short => '-P', :long => '--password PASSWORD',
|
22
|
+
:type => String, :key => :password,
|
23
|
+
:description => 'hAPI Password',
|
24
|
+
:required => true
|
25
|
+
},
|
26
|
+
|
27
|
+
{
|
28
|
+
:short => '-H', :long => '--hostname HOSTNAME',
|
29
|
+
:type => String, :key => :hostname,
|
30
|
+
:description => 'hAPI Server Hostname',
|
31
|
+
:default => 'api.voxel.net'
|
32
|
+
},
|
33
|
+
|
34
|
+
{
|
35
|
+
:short => '-V', :long => '--version VERSION',
|
36
|
+
:type => String, :key => :version,
|
37
|
+
:description => 'hAPI Version',
|
38
|
+
:default => '1.0'
|
39
|
+
},
|
40
|
+
|
41
|
+
{
|
42
|
+
:short => nil, :long => '--[no-]useauthkey',
|
43
|
+
:type => nil, :key => :useauthkey,
|
44
|
+
:description => 'Are username and password actually key and secret?',
|
45
|
+
:default => false
|
46
|
+
},
|
47
|
+
|
48
|
+
{
|
49
|
+
:short => '-m', :long => '--method METHOD',
|
50
|
+
:type => String, :key => :method,
|
51
|
+
:description => 'hAPI Method Name',
|
52
|
+
:required => true
|
53
|
+
},
|
54
|
+
|
55
|
+
{
|
56
|
+
:short => '-y', :long => '--yaml-param PARAMFILE',
|
57
|
+
:type => String, :key => :yaml_param,
|
58
|
+
:description => 'hAPI Method Paramaters in YAML Format (FILENAME)'
|
59
|
+
},
|
60
|
+
|
61
|
+
{
|
62
|
+
:short => '-r', :long => '--repeat INTERVAL',
|
63
|
+
:type => String, :key => :repeat,
|
64
|
+
:description => 'Should the method be repeated (loop time in seconds)'
|
65
|
+
},
|
66
|
+
|
67
|
+
{
|
68
|
+
:short => '-o', :long => '--output-format FORMAT',
|
69
|
+
:type => String, :key => :output_format,
|
70
|
+
:description => 'Output format (Defaults to Ruby Array/Hash)',
|
71
|
+
:default => :xml
|
72
|
+
},
|
73
|
+
]
|
74
|
+
|
75
|
+
begin
|
76
|
+
parsed_options = {}
|
77
|
+
|
78
|
+
option_parser = OptionParser.new do |op|
|
79
|
+
op.set_summary_indent(' ')
|
80
|
+
op.banner = "Usage: #{$0} [OPTIONS] [METHOD PARAM]"
|
81
|
+
op.separator " "
|
82
|
+
|
83
|
+
option_data.each do |opt|
|
84
|
+
if opt[:short].nil?
|
85
|
+
op.on( opt[:long], opt[:type], opt[:description]) do |s|
|
86
|
+
parsed_options[opt[:key]] = s
|
87
|
+
end
|
88
|
+
else
|
89
|
+
op.on( opt[:short], opt[:long], opt[:type], opt[:description]) do |s|
|
90
|
+
parsed_options[opt[:key]] = s
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
parsed_options[opt[:key]] = opt[:default] if opt.has_key?(:default)
|
95
|
+
end
|
96
|
+
|
97
|
+
op.on_tail('-x', '--[no-]debug', "Enable Debugging Output") { |s| parsed_options[:debug] = s }
|
98
|
+
op.on_tail('-h', '--help', "Displays help message") { puts op; exit 0; }
|
99
|
+
end
|
100
|
+
|
101
|
+
if ENV.has_key?('HAPI_CONFIG') and File.readable?(ENV['HAPI_CONFIG'])
|
102
|
+
File.open(ENV['HAPI_CONFIG']) { |fh| parsed_options.merge! YAML::load( fh ) }
|
103
|
+
end
|
104
|
+
|
105
|
+
option_parser.parse!(ARGV)
|
106
|
+
|
107
|
+
option_data.each do |od|
|
108
|
+
if od.has_key?(:required) and not parsed_options.has_key?(od[:key])
|
109
|
+
raise StandardError, "#{od[:long].split(' ')[0]} is a required parameter"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
if parsed_options[:useauthkey] #and not parsed_options.has_key?(:username)
|
114
|
+
api = HAPI.new( parsed_options.merge( { :authkey => { :key => parsed_options[:username], :secret => parsed_options[:password] } } ) )
|
115
|
+
else
|
116
|
+
hapi_options = {}
|
117
|
+
|
118
|
+
[ :username, :password, :version, :debug, :hostname ].each do |k|
|
119
|
+
hapi_options[k] = parsed_options[k]
|
120
|
+
end
|
121
|
+
|
122
|
+
api = HAPI.new hapi_options
|
123
|
+
end
|
124
|
+
|
125
|
+
mparam = Hash.new
|
126
|
+
|
127
|
+
if parsed_options.has_key?(:yaml_param) and File.readable?(parsed_options[:yaml_param])
|
128
|
+
File.open(parsed_options[:yaml_param]) { |fh| mparam.reverse_merge! YAML::load( fh ) }
|
129
|
+
end
|
130
|
+
|
131
|
+
#pp ARGV
|
132
|
+
if ARGV.length > 0
|
133
|
+
#if parsed_options.has_key?(:method_param)
|
134
|
+
ARGV.each do |kval|
|
135
|
+
key, value = kval.split('=')
|
136
|
+
mparam[key.to_sym] = value
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
api_method_name = api.translate_api_to_method( parsed_options[:method] )
|
141
|
+
|
142
|
+
if parsed_options.has_key?(:repeat)
|
143
|
+
while true
|
144
|
+
puts api.send(api_method_name, mparam, { :format => :xml } )
|
145
|
+
$stdout.flush
|
146
|
+
sleep parsed_options[:repeat].to_i
|
147
|
+
end
|
148
|
+
else
|
149
|
+
puts api.send(api_method_name, mparam, { :format => :xml } )
|
150
|
+
end
|
151
|
+
rescue OptionParser::ParseError => e
|
152
|
+
STDERR.puts e
|
153
|
+
exit 1
|
154
|
+
# rescue HAPI::Backend => ex
|
155
|
+
# STDERR.puts "[ERROR] (#{ex.class.to_s}) #{ex.message}"
|
156
|
+
# exit 1
|
157
|
+
rescue StandardError => ex
|
158
|
+
STDERR.puts "[ERROR] (#{ex.class.to_s}) #{ex.message}"
|
159
|
+
STDERR.puts ex.backtrace
|
160
|
+
exit 1
|
161
|
+
rescue RuntimeError => ex
|
162
|
+
STDERR.puts ex.message
|
163
|
+
exit 1
|
164
|
+
rescue SystemExit
|
165
|
+
exit 0
|
166
|
+
rescue Exception => ex
|
167
|
+
STDERR.puts "Unhandled Exception: #{ex.class.to_s}"
|
168
|
+
STDERR.printf ex.message + "\n\n"
|
169
|
+
STDERR.puts "Backtrace:"
|
170
|
+
STDERR.puts ex.backtrace
|
171
|
+
exit 1
|
172
|
+
end
|
173
|
+
|
data/lib/hapi.rb
ADDED
@@ -0,0 +1,216 @@
|
|
1
|
+
# The library provides the class HAPI, a ruby interface to Voxel's hAPI
|
2
|
+
# http://api.voxel.net/docs
|
3
|
+
#
|
4
|
+
# Author:: James W. Brinkerhoff (mailto:jwb@voxel.net)
|
5
|
+
# Copyright:: Copyright (c) 2009 Voxel dot Net, Inc.
|
6
|
+
# License:: Unknown
|
7
|
+
|
8
|
+
require 'xmlsimple'
|
9
|
+
require 'net/https'
|
10
|
+
require 'uri'
|
11
|
+
require 'yaml'
|
12
|
+
require 'time'
|
13
|
+
require 'digest/md5'
|
14
|
+
|
15
|
+
class String
|
16
|
+
def underscore
|
17
|
+
self.to_s.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
18
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
19
|
+
tr("-", "_").
|
20
|
+
downcase
|
21
|
+
end
|
22
|
+
|
23
|
+
def camelize(first_letter_in_uppercase = true)
|
24
|
+
if first_letter_in_uppercase
|
25
|
+
self.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
26
|
+
else
|
27
|
+
self.first.downcase + camelize(self)[1..-1]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Hash
|
33
|
+
def reverse_merge(other_hash)
|
34
|
+
other_hash.merge(self)
|
35
|
+
end
|
36
|
+
|
37
|
+
def reverse_merge!(other_hash)
|
38
|
+
replace(reverse_merge(other_hash))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class HAPI
|
43
|
+
#Library Version
|
44
|
+
LIB_VERSION = '1.1.6'
|
45
|
+
#UserAgent for Logging
|
46
|
+
USER_AGENT = "Voxel hAPI ruby Client; #{LIB_VERSION}"
|
47
|
+
|
48
|
+
#API Username
|
49
|
+
attr_accessor :username
|
50
|
+
#API Password
|
51
|
+
attr_accessor :password
|
52
|
+
#API Host, Defaults to api.voxel.net
|
53
|
+
attr_accessor :hostname
|
54
|
+
#API Endpoint Version, Defaults to '1.0'
|
55
|
+
attr_accessor :version
|
56
|
+
#Debug Mode, When true outputs debugging info via STDERR
|
57
|
+
attr_accessor :debug
|
58
|
+
#Are username and password a key/secret pair
|
59
|
+
attr_accessor :useauthkey
|
60
|
+
#Default formatting for return values, :xml or :ruby
|
61
|
+
attr_accessor :default_format
|
62
|
+
|
63
|
+
def initialize(options = {})
|
64
|
+
options.reverse_merge! :filename => nil
|
65
|
+
|
66
|
+
unless options[:filename].nil?
|
67
|
+
raise "#{options[:filename]} is not readable!" unless File.readable?(options[:filename])
|
68
|
+
|
69
|
+
yaml_options = YAML.load(File.read(options[:filename]))
|
70
|
+
options.merge!( yaml_options )
|
71
|
+
end
|
72
|
+
|
73
|
+
options.reverse_merge! :hostname => 'api.voxel.net', :debug => false,
|
74
|
+
:version => '1.0', :useauthkey => false, :default_format => :xml
|
75
|
+
|
76
|
+
validate_required_options options, :username, :password
|
77
|
+
|
78
|
+
[ :username, :password, :hostname, :version, :debug, :useauthkey, :default_format ].each do |k|
|
79
|
+
send( "#{k.to_s}=", options[k] )
|
80
|
+
end
|
81
|
+
|
82
|
+
begin
|
83
|
+
unless @useauthkey
|
84
|
+
keypair = voxel_hapi_authkeys_read( {}, { :format => :ruby, :http_auth => true } )['authkey']
|
85
|
+
|
86
|
+
@username = keypair['key']
|
87
|
+
@password = keypair['secret']
|
88
|
+
end
|
89
|
+
rescue Exception => ex
|
90
|
+
raise ex
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.new_from_config(filename, additional_options = {})
|
95
|
+
STDERR.puts "WARNING: new_from_config is no longer in use"
|
96
|
+
|
97
|
+
new(additional_options)
|
98
|
+
end
|
99
|
+
|
100
|
+
def helper_devices_status()
|
101
|
+
voxcloud = voxel_voxcloud_status['devices']
|
102
|
+
voxservers = voxel_voxservers_status['devices']
|
103
|
+
|
104
|
+
statuses = voxcloud['device'] unless voxcloud.empty?
|
105
|
+
statuses += voxservers['device'] unless voxservers.empty?
|
106
|
+
|
107
|
+
devices = {}
|
108
|
+
|
109
|
+
statuses.each { |s| devices[s['id']] = s['status'] }
|
110
|
+
|
111
|
+
devices
|
112
|
+
end
|
113
|
+
|
114
|
+
def translate_api_to_method( api_method_name )
|
115
|
+
ruby_method_name = []
|
116
|
+
|
117
|
+
api_method_name.split(".").each do |mp|
|
118
|
+
if mp.include?("_")
|
119
|
+
ruby_method_name << mp.camelize
|
120
|
+
else
|
121
|
+
ruby_method_name << mp
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
ruby_method_name.join("_")
|
126
|
+
end
|
127
|
+
|
128
|
+
def translate_method_to_api( method_name )
|
129
|
+
method_name.split("_").map { |mp| mp.underscore }.join(".")
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
def request_method( method_name, method_arguments = {}, options = {} )
|
135
|
+
options.reverse_merge! :format => @default_format, :http_auth => false
|
136
|
+
|
137
|
+
STDERR.puts "Calling method #{method_name}" if debug
|
138
|
+
|
139
|
+
begin
|
140
|
+
signed_request = sign_request( method_name, method_arguments )
|
141
|
+
response = make_http_request( signed_request, options[:http_auth] )
|
142
|
+
STDERR.puts response if @debug
|
143
|
+
return process_response( response, options[:format] )
|
144
|
+
rescue Exception => ex
|
145
|
+
raise ex
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def method_missing( method_id, *args )
|
150
|
+
STDERR.puts "Calling method #{method_id.id2name}" if @debug
|
151
|
+
|
152
|
+
request_method( translate_method_to_api(method_id.id2name), *args )
|
153
|
+
end
|
154
|
+
|
155
|
+
def validate_required_options( all, *required )
|
156
|
+
required.each do |opt|
|
157
|
+
raise ":#{opt} must be specified" unless all.has_key?(opt)
|
158
|
+
raise ":#{opt} must be non-NULL" if all[opt].nil?
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def make_http_request( options = {}, auth = false )
|
163
|
+
url = URI.parse( "https://#{@hostname}/version/#{@version}" )
|
164
|
+
|
165
|
+
request = Net::HTTP::Post.new(url.path)
|
166
|
+
|
167
|
+
request.basic_auth( @username, @password ) if auth
|
168
|
+
request.set_form_data(options)
|
169
|
+
request.add_field('User-Agent', USER_AGENT)
|
170
|
+
|
171
|
+
http = Net::HTTP.new(url.host, url.port)
|
172
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
173
|
+
http.use_ssl = true
|
174
|
+
response = http.request(request)
|
175
|
+
|
176
|
+
case response
|
177
|
+
when Net::HTTPSuccess, Net::HTTPRedirection
|
178
|
+
response.body
|
179
|
+
when Net::HTTPUnauthorized
|
180
|
+
STDERR.puts response.class.to_s if @debug
|
181
|
+
raise "Invalid Username or Password"
|
182
|
+
else
|
183
|
+
STDERR.puts response.class.to_s if @debug
|
184
|
+
raise "Invalid response code from API endpoint"
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def sign_request( method_name, method_arguments = {} )
|
189
|
+
request = method_arguments.clone
|
190
|
+
request[:method] = method_name
|
191
|
+
request[:timestamp] = Time.now.xmlschema
|
192
|
+
request[:key] = @username
|
193
|
+
request[:api_sig] = Digest::MD5.hexdigest( @password + create_param_string(request) )
|
194
|
+
|
195
|
+
request
|
196
|
+
end
|
197
|
+
|
198
|
+
def create_param_string( params = {} )
|
199
|
+
params.keys.map { |k| k.to_s }.sort.map { |k| "#{k}#{params[k.to_sym]}" }.join("")
|
200
|
+
end
|
201
|
+
|
202
|
+
def process_xml_document( xml_data, options = {} )
|
203
|
+
options.reverse_merge! 'ForceArray' => false
|
204
|
+
XmlSimple.xml_in(xml_data, options)
|
205
|
+
end
|
206
|
+
|
207
|
+
def process_response( document, format )
|
208
|
+
case format
|
209
|
+
when :ruby
|
210
|
+
return process_xml_document( document )
|
211
|
+
when :xml
|
212
|
+
return document
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: voxel-hapi
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.1.6
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- James W. Brinkerhoff
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-09-30 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: xml-simple
|
16
|
+
requirement: &9669620 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.0.12
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *9669620
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: libxml-ruby
|
27
|
+
requirement: &9669160 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.0.3
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *9669160
|
36
|
+
description:
|
37
|
+
email: jwb@voxel.net
|
38
|
+
executables:
|
39
|
+
- rhapi
|
40
|
+
extensions: []
|
41
|
+
extra_rdoc_files: []
|
42
|
+
files:
|
43
|
+
- lib/hapi.rb
|
44
|
+
- bin/rhapi
|
45
|
+
- examples/hapirc.example
|
46
|
+
homepage: http://voxel.net/
|
47
|
+
licenses: []
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ! '>='
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
requirements: []
|
65
|
+
rubyforge_project:
|
66
|
+
rubygems_version: 1.8.10
|
67
|
+
signing_key:
|
68
|
+
specification_version: 3
|
69
|
+
summary: A Ruby Class Interface to Voxel's hAPI
|
70
|
+
test_files: []
|