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.
Files changed (4) hide show
  1. data/bin/rhapi +173 -0
  2. data/examples/hapirc.example +7 -0
  3. data/lib/hapi.rb +216 -0
  4. 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
+
@@ -0,0 +1,7 @@
1
+ ---
2
+ :username: 1234
3
+ :password: passw0rd
4
+ :hostname: api.voxel.net
5
+ :version: 1.0
6
+ :debug: false
7
+ :useauthkey: false
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: []