cloudstack_api 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +123 -0
  4. data/Rakefile +32 -0
  5. data/lib/cloudstack/api.rb +179 -0
  6. data/lib/cloudstack/configuration.rb +59 -0
  7. data/lib/cloudstack/railtie.rb +10 -0
  8. data/lib/cloudstack/version.rb +3 -0
  9. data/lib/cloudstack.rb +19 -0
  10. data/lib/config/api_spec.yml +6293 -0
  11. data/lib/tasks/cloudstack_tasks.rake +11 -0
  12. data/lib/tasks/config/cloudstack.yml +14 -0
  13. data/test/api_test.rb +67 -0
  14. data/test/cloudstack_test.rb +12 -0
  15. data/test/dummy/README.rdoc +28 -0
  16. data/test/dummy/Rakefile +6 -0
  17. data/test/dummy/app/assets/javascripts/application.js +13 -0
  18. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  19. data/test/dummy/app/controllers/application_controller.rb +5 -0
  20. data/test/dummy/app/helpers/application_helper.rb +2 -0
  21. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  22. data/test/dummy/bin/bundle +3 -0
  23. data/test/dummy/bin/rails +4 -0
  24. data/test/dummy/bin/rake +4 -0
  25. data/test/dummy/config/application.rb +23 -0
  26. data/test/dummy/config/boot.rb +5 -0
  27. data/test/dummy/config/cloudstack.yml +13 -0
  28. data/test/dummy/config/database.yml +25 -0
  29. data/test/dummy/config/environment.rb +5 -0
  30. data/test/dummy/config/environments/development.rb +38 -0
  31. data/test/dummy/config/environments/production.rb +82 -0
  32. data/test/dummy/config/environments/test.rb +39 -0
  33. data/test/dummy/config/initializers/assets.rb +8 -0
  34. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  35. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  36. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  37. data/test/dummy/config/initializers/inflections.rb +16 -0
  38. data/test/dummy/config/initializers/mime_types.rb +4 -0
  39. data/test/dummy/config/initializers/session_store.rb +3 -0
  40. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  41. data/test/dummy/config/locales/en.yml +23 -0
  42. data/test/dummy/config/routes.rb +56 -0
  43. data/test/dummy/config/secrets.yml +22 -0
  44. data/test/dummy/config.ru +4 -0
  45. data/test/dummy/db/test.sqlite3 +0 -0
  46. data/test/dummy/log/development.log +0 -0
  47. data/test/dummy/log/test.log +705 -0
  48. data/test/dummy/public/404.html +67 -0
  49. data/test/dummy/public/422.html +67 -0
  50. data/test/dummy/public/500.html +66 -0
  51. data/test/dummy/public/favicon.ico +0 -0
  52. data/test/test_helper.rb +15 -0
  53. metadata +220 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1a71fad5fd898a7b1c3660a34b5c9eb251049ebb
4
+ data.tar.gz: 3045b355845b58ccb21352d8f394cff175a0a921
5
+ SHA512:
6
+ metadata.gz: 864c0045e465200e2e5b6b41706b5c8dfbfd6aedc630a552f2b4c29d833531db344d3a0b624e8373855b56276c76ef19470886ac93f6092c79d0aa852df59467
7
+ data.tar.gz: c8f27faf0e8175c605f32435fd2b4f17886e5c11cc18ceda50d829099e6b3067b62e465885b0ac16ba8ac2426de7ab37fb71f9aee61807b4da687a7ade358cff
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2014 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,123 @@
1
+ = cloudstack_api
2
+
3
+ Ruby library for the CloudStack API. Current version uses CloudStack API
4
+ version 4.3.0 ({API Reference}[http://cloudstack.apache.org/docs/api/apidocs-4.3/TOC_Root_Admin.html])
5
+
6
+
7
+ == Installation
8
+
9
+ Cloudstack library requires Ruby 1.9.3 or later. To install, add this line to your
10
+ +Gemfile+ and run <tt>bundle install</tt>:
11
+
12
+ gem 'cloudstack_api'
13
+
14
+ Gem was renamed from +cloudstack+ to +cloudstack_api+ to avoid name conflicts with CloudStack trademark.
15
+
16
+
17
+ == Configuration
18
+
19
+ By default module uses configuration from <tt>config/cloudstack.yml</tt> file. To generate it run:
20
+
21
+ rake cloudstack:install
22
+
23
+ This command generates file <tt>config/cloudstack.yml</tt> which contains configuration for each environment
24
+
25
+ default: &default
26
+ api_key: xxxxxxxxx
27
+ secret_key: xxxxxxxxx
28
+ api_url: https://your.domain.com/client/api
29
+
30
+ production:
31
+ <<: *default
32
+
33
+ development:
34
+ <<: *default
35
+
36
+ test:
37
+ <<: *default
38
+ api_mode: test
39
+
40
+ Fill in +api_url+, +api_key+ and +secret_key+ for each environment or use +default+ template.
41
+
42
+ You can configure module at runtime if you need:
43
+
44
+ Cloudstack.configure do |config|
45
+ config.api_key = 'xxxxx'
46
+ config.secret_key = 'xxxxxx'
47
+ config.api_url = 'https://your.domain.com/client/api'
48
+ config.api_mode = 'test' # don't execute any command, just return signed request
49
+ end
50
+
51
+ or set each configuration option separately:
52
+
53
+ Cloudstack.config.api_key = 'xxxxxx'
54
+
55
+ == Usage
56
+
57
+ To use library you need to create an instance of Cloudstack::Api class and call it methods:
58
+
59
+ @api = Cloudstack::Api.new
60
+ @api.execute_command('listVirtualMachines', account: 'myuser', domainid: '1bd9a980-0f11-4892-aa0b-7c434dbd6d1c')
61
+
62
+ Last line returns a result hash:
63
+
64
+ {
65
+ :count => 1,
66
+ :virtualmachine => [
67
+ {
68
+ :id => "0e061655-d982-4c2d-810e-6f3aa7d2ccde",
69
+ :name => "cloudvds4509",
70
+ :displayname => "cloudvds4509",
71
+ :account => "myuser",
72
+ :domainid => "1bd9a980-0f11-4892-aa0b-7c434dbd6d1c",
73
+ :domain => "core",
74
+ :created => "2014-08-15T15:35:20+0000",
75
+ :state => "Stopped",
76
+ :haenable => true,
77
+ ...
78
+
79
+ or +false+ if an error was occured. All error messages stores in an +errors+ method.
80
+ +errors+ method is a functionality of <tt>ActiveModel::Validations</tt> module.
81
+
82
+ @api.errors.messages
83
+
84
+ returns:
85
+
86
+ {:base=>["could not find account user in domain 1bd9a980-0f11-4892-aa0b-7c434dbd6d1c"]}
87
+
88
+ If you prefer to work with exceptions you can use functions with <tt>!</tt> suffix. For example:
89
+
90
+ begin
91
+ @api = Cloudstack::Api.new
92
+ @api.execute_command!('listVirtualMachines', account: 'myuser', domainid: '1bd9a980-0f11-4892-aa0b-7c434dbd6d1c')
93
+ rescue Exception => e
94
+ puts e.message # => could not find account myuser in domain 1bd9a980-0f11-4892-aa0b-7c434dbd6d1c
95
+ end
96
+
97
+
98
+ == Shortcuts for API commands
99
+
100
+ For writing more readable code, use API commands shortcuts.
101
+
102
+ @api.list_virtual_machines(account: 'myuser', domainid: '1bd9a980-0f11-4892-aa0b-7c434dbd6d1c')
103
+
104
+ is the same as:
105
+
106
+ @api.execute_command('listVirtualMachines', account: 'myuser', domainid: '1bd9a980-0f11-4892-aa0b-7c434dbd6d1c')
107
+
108
+ Shortcuts commands is an underscored API commands. For example command +deployVirtualMachine+
109
+ has +deploy_virtual_machine+ shortcut method.
110
+
111
+ Exception raises methods have shotcuts too. Just add <tt>!</tt> suffix to it:
112
+
113
+ begin
114
+ @api.list_virtual_machines!(account: 'myuser', domainid: '1bd9a980-0f11-4892-aa0b-7c434dbd6d1c')
115
+ rescue Exception => e
116
+ puts e.message # => could not find account myuser in domain 1bd9a980-0f11-4892-aa0b-7c434dbd6d1c
117
+ end
118
+
119
+
120
+ ====== TODO
121
+
122
+ - Handle async jobs
123
+ - Write documentation!!!
data/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Cloudstack'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+
18
+
19
+
20
+ Bundler::GemHelper.install_tasks
21
+
22
+ require 'rake/testtask'
23
+
24
+ Rake::TestTask.new(:test) do |t|
25
+ t.libs << 'lib'
26
+ t.libs << 'test'
27
+ t.pattern = 'test/**/*_test.rb'
28
+ t.verbose = false
29
+ end
30
+
31
+
32
+ task default: :test
@@ -0,0 +1,179 @@
1
+ module Cloudstack
2
+
3
+ # Instance of Cloudstack::Api class uses to prepare CloudStack API request
4
+ # and send it to Cloudstack Management Server. Then Cludstack::Api handles
5
+ # a response and return it in usable type.
6
+
7
+ class Api
8
+ include ActiveModel::AttributeMethods
9
+ include ActiveModel::Validations
10
+
11
+ validate :validate_api_params
12
+
13
+ # methods with '!' suffix will raise an exception when error occurs
14
+ attribute_method_suffix '!'
15
+
16
+ # add api commands as methods to api instance
17
+ define_attribute_methods Cloudstack.config.methods
18
+
19
+
20
+ # Calls CloudStack API command on a remote server.
21
+ # Method arguments is +command+ and a list of +params+
22
+ # - +command+ is one of the commands described on a
23
+ # {CloudStack API reference page}[http://cloudstack.apache.org/docs/api/apidocs-4.3/TOC_Root_Admin.html]
24
+ # - +params+ is a hash of +command+ parameters
25
+ #
26
+ # Method returns a response hash (if request was success) or
27
+ # raises an error else. Before raising an error it stores in an
28
+ # #errors object. It works like ActiveModel::Validations
29
+ # So error messages can be retrieved:
30
+ # api_instance.errors.messages
31
+ #
32
+ # == Example
33
+ #
34
+ # @api = Cloudstack::Api.new
35
+ # @api.execute_command('some command', some_param: 'some value') # => raises ArgumentError exception
36
+ # @api.errors.messages # => {:command=>["'some command' is invalid command"]}
37
+ #
38
+
39
+ def execute_command!(command, params={})
40
+ # Clear errors from previous request
41
+ errors.clear
42
+
43
+ # Set local variables
44
+ @command = command.to_s
45
+ @params = params.symbolize_keys
46
+
47
+ # Validate command and parameters
48
+ raise ArgumentError if invalid?
49
+
50
+ # Assemble request_options
51
+ request_options = {
52
+ command: @command,
53
+ apikey: Cloudstack.config.api_key,
54
+ response: 'json'
55
+ }
56
+ request_options.merge! @params
57
+
58
+ # Generate request signature and add it to request_options
59
+ request_options[:signature] = sign_request(request_options)
60
+
61
+ # Generate request url with parameters
62
+ request_url = Cloudstack.config.api_url + '?' + request_options.to_query
63
+
64
+ # Check current mode. If mode is +test+ than command not executes,
65
+ # but only returns request url.
66
+ return request_url if Cloudstack.config.api_mode == 'test'
67
+
68
+ begin
69
+ # send curl request to the CloudStack server
70
+ curl = Curl::Easy.new(request_url)
71
+ curl.ssl_verify_peer = false
72
+ curl.perform
73
+
74
+ # handle response
75
+ response = JSON.parse(curl.body_str, symbolize_names: true)
76
+ rescue Exception => e
77
+ # If error occurs, it adds to instance errors
78
+ errors.add(:base, e.message)
79
+ raise e
80
+ end
81
+
82
+ # Checks if a command returns a CloudStack error. If error occurs,
83
+ # it adds to errors and excepions reises.
84
+ if response[:errorresponse].present?
85
+ errors.add(:base, response[:errorresponse][:errortext])
86
+ raise StandardError, response[:errorresponse][:errortext]
87
+ end
88
+
89
+ result = response["#{command}response".downcase.to_sym]
90
+
91
+ if result[:errortext].present?
92
+ errors.add(:base, result[:errortext])
93
+ raise StandardError, result[:errortext]
94
+ end
95
+
96
+ # If all pass success, returns a result hash
97
+ result
98
+ end
99
+
100
+ # Method provides the same functionality as #execute_command! except
101
+ # it does not raises exceptions. When error occurs, it just returns +false+.
102
+ # So if method returns +false+, errors message should be in #errors
103
+ #
104
+ # == Example
105
+ #
106
+ # @api = Cloudstack::Api.new
107
+ # @api.execute_command('some command', some_param: 'some value') # => false
108
+ # @api.errors.messages # => {:command=>["'some command' is invalid command"]}
109
+ #
110
+
111
+ def execute_command(command, options={})
112
+ begin
113
+ return execute_command!(command, options)
114
+ rescue
115
+ return false
116
+ end
117
+ end
118
+
119
+ protected
120
+
121
+ # Generates a signature for api request.
122
+ # Details {Signing API Requests}[http://cloudstack.apache.org/docs/en-US/Apache_CloudStack/4.0.0-incubating/html/API_Developers_Guide/signing-api-requests.html]
123
+ def sign_request(request_options)
124
+ request_string = Hash[request_options.sort].to_query.downcase
125
+ Base64.encode64(OpenSSL::HMAC.digest( OpenSSL::Digest.new('sha1'), Cloudstack.config.secret_key, request_string)).strip
126
+ end
127
+
128
+ # Method provides shortcuts for api commands. For example
129
+ # it can be called
130
+ # @api.list_virtual_machines!(parameters)
131
+ # instead of
132
+ # @api.execute_command!('listVirtualMachines', parameters)
133
+ #
134
+ # It's useful in a command line mode. Function names will autocompletes.
135
+ def attribute!(attr, *args)
136
+ execute_command!(Cloudstack.config.method_to_command(attr), *args)
137
+ end
138
+
139
+ # The same as #attribute! method, but calls #execute_command method,
140
+ # which doesn't raise errors.
141
+ def attribute(attr, *args)
142
+ execute_command(Cloudstack.config.method_to_command(attr), *args)
143
+ end
144
+
145
+ # Validates api command and parameters. CloudStack API specification
146
+ # stores in a lib/config/api_spec.yml file. Method checks command name,
147
+ # parameters names and required parameters presence.
148
+ #
149
+ # If command or parameters is invalid, then corresponding error will be
150
+ # added to #errors
151
+ def validate_api_params
152
+ # retrieve parameters that belongs
153
+ allowed_params = Cloudstack.config.command_params(@command)
154
+
155
+ # if no parameters for current command then command is incorrect
156
+ unless allowed_params.present?
157
+ errors.add(:command, "'#{@command}' is invalid command")
158
+ return
159
+ end
160
+
161
+ # each parameter name should be in allowed parameters for current command
162
+ (@params.keys - allowed_params).each do |param|
163
+ errors.add(:params, "'#{param}' parameter is not allowed")
164
+ end
165
+
166
+ # check required parameters
167
+ required_params = Cloudstack.config.command_required_params(@command)
168
+ (required_params - @params.keys).each do |param|
169
+ errors.add(:params, "'#{param}' parameter is required")
170
+ end
171
+
172
+ # # each parameter value should be a string
173
+ # @params.each do |key,value|
174
+ # errors.add(:params, "'#{value}' parameter should be a String") unless value.is_a? String
175
+ # end
176
+ end
177
+
178
+ end
179
+ end
@@ -0,0 +1,59 @@
1
+ class Configuration
2
+
3
+ attr_accessor :api_key,
4
+ :secret_key,
5
+ :api_url,
6
+ :api_spec,
7
+ :api_mode
8
+
9
+ def initialize
10
+ if defined?(Rails) and File.exist?(File.expand_path('config/cloudstack.yml', Rails.root))
11
+ config = YAML.load_file(File.expand_path('config/cloudstack.yml', Rails.root))[Rails.env]
12
+
13
+ @api_key = config['api_key'] if config['api_key'].present?
14
+ @secret_key = config['secret_key'] if config['secret_key'].present?
15
+ @api_url = config['api_url'] if config['api_url'].present?
16
+ @api_mode = config['api_mode'] if config['api_mode'].present?
17
+ end
18
+ @api_spec = YAML.load_file(File.expand_path('../../config/api_spec.yml', __FILE__))
19
+
20
+ # contain map of underscore api commands relative to camelize
21
+ @method_to_command = Hash[ @api_spec.keys.map {|key| [key.underscore.to_sym, key]} ]
22
+ end
23
+
24
+
25
+ ##
26
+ # return an array of api methods. Method is an underscored command.
27
+ # it was done, because ruby methods should be underscored, but CloudStack
28
+ # uses camelCased command names
29
+ def methods
30
+ @api_methods ||= @api_spec.keys.map{ |key| key.underscore.to_sym }
31
+ end
32
+
33
+
34
+ ##
35
+ # return an array of available api commands. It uses in validations and so on.
36
+ def commands
37
+ @api_commands ||= @api_spec.keys
38
+ end
39
+
40
+
41
+ ##
42
+ # convert underscored method name to camelCased command
43
+ def method_to_command(command)
44
+ @method_to_command[command.to_sym]
45
+ end
46
+
47
+ ##
48
+ # return an array of given command parameters
49
+ def command_params(command)
50
+ @api_spec[command.to_s]['params'].keys.map(&:to_sym) if @api_spec[command.to_s]
51
+ end
52
+
53
+ ##
54
+ # return an array of given command required parameters
55
+ def command_required_params(command)
56
+ @api_spec[command.to_s]['params'].map {|k,v| k.to_sym if v['required']}.compact
57
+ end
58
+
59
+ end
@@ -0,0 +1,10 @@
1
+ require 'rails'
2
+ module Cloudstack
3
+ class Railtie < Rails::Railtie
4
+ railtie_name :cloudstack
5
+
6
+ rake_tasks do
7
+ load 'tasks/cloudstack_tasks.rake'
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ module Cloudstack
2
+ VERSION = '1.0.0'
3
+ end
data/lib/cloudstack.rb ADDED
@@ -0,0 +1,19 @@
1
+ module Cloudstack
2
+ require 'cloudstack/railtie' if defined?(Rails)
3
+ require 'cloudstack/configuration'
4
+ autoload :Api, 'cloudstack/api'
5
+ require 'curb'
6
+
7
+ # Gives access to the current configuration
8
+ def self.config
9
+ @configuration ||= Configuration.new
10
+ end
11
+
12
+ # Allows easy setting of multiple configuration options. See Configuration
13
+ # for all available options.
14
+ def self.configure
15
+ config = self.config
16
+ yield(config)
17
+ end
18
+
19
+ end