cloudstack_api 1.0.0

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 (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