voxel-hapi 1.1.6

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