voxel-hapi 1.1.6
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|