cloud_stack_client 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "cloud_stack_client/cli"
4
+ cli = CloudStackClient::CLI.new
5
+ result = cli.run $0, ARGV
6
+ exit 1 unless result
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'cloud_stack_client/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "cloud_stack_client"
8
+ gem.version = CloudStackClient::VERSION
9
+ gem.authors = ["Roman Messer"]
10
+ gem.email = ["roman.messer@ict-cloud.com"]
11
+ gem.description = %q{Execute any command against a CloudStack Infrastructure. Also contains a commandline utility with auto paging and support to read credentials from a config file.}
12
+ gem.summary = %q{A simple library to work with the CloudStack API.}
13
+ # gem.homepage = ""
14
+
15
+ if File.exist? '.git'
16
+ gem.files = `git ls-files`.split($/)
17
+ elsif File.exist? '.hg'
18
+ gem.files = `hg manifest`.split($/)
19
+ end
20
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
21
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
22
+ gem.require_paths = ["lib"]
23
+ end
@@ -0,0 +1,8 @@
1
+ require "cloud_stack_client/version"
2
+ require "cloud_stack_client/definitions"
3
+ require "cloud_stack_client/api"
4
+ require "cloud_stack_client/connector"
5
+
6
+ # Execute queries against the CloudStack API
7
+ module CloudStackClient
8
+ end
@@ -0,0 +1,325 @@
1
+ # An API to interact with the Citrix CloudStack Platform
2
+
3
+ require "cloud_stack_client/helpers"
4
+ require "cloud_stack_client/definitions"
5
+ require "digest/md5"
6
+ autoload :URI, "uri"
7
+ autoload :OpenSSL, "openssl"
8
+ autoload :Base64, "base64"
9
+ autoload :JSON, "json"
10
+ # Autoloading Net submodules
11
+ # @private
12
+ module Net
13
+ autoload :HTTP, "net/http"
14
+ end
15
+ # Autoloading REXML submodules
16
+ # @private
17
+ module REXML
18
+ autoload :Document, "rexml/document"
19
+ autoload :QuickPath, "rexml/quickpath"
20
+ end
21
+
22
+ module CloudStackClient
23
+ # Collection of functions to work with the API
24
+ module API
25
+
26
+ # Login with username, password hash and domain to get session credentials
27
+ #
28
+ # @param api_uri [String] HTTP URL of the CloudStack API
29
+ # @param username [String] CloudStack login user
30
+ # @param hashed_password [String] CloudStack login password (use {CloudStackClient::Helpers::hash_password})
31
+ # @param domain [String] CloudStack login domain
32
+ # @param http_method [Symbol] The HTTP method to submit the query (:post or :get)
33
+ # @param ssl_check [Boolean] Check Server SSL certificate?
34
+ # @return [Hash] Credentials for subsequent queries
35
+ # @see CloudStackClient::Helpers::hash_password
36
+ def self.get_credentials(api_uri, username, hashed_password, domain = "", http_method = nil , ssl_check = nil)
37
+ http_method ||= API_DEFAULTS[:http_method]
38
+ ssl_check ||= API_DEFAULTS[:ssl_check]
39
+ parameters = {:command => "login", :response => "json", :username => username, :password => hashed_password, :domain => parse_domain(domain)}
40
+ headers, body = execute_query(api_uri, generate_query(parameters), [], http_method, ssl_check)
41
+ credentials = {}
42
+ if headers.has_key? "set-cookie" # Expect lowercase names from Net::HTTPHeader
43
+ credentials[:session_cookies] = headers["set-cookie"].map {|raw| raw.split(";").first}
44
+ end
45
+ json = JSON.parse body
46
+ return nil unless CloudStackClient::API::response_is_success? json
47
+ credentials[:session_key] = json["loginresponse"]["sessionkey"]
48
+ credentials[:user_id] = json["loginresponse"]["userid"]
49
+ credentials
50
+ end
51
+
52
+ # Test connection with supplied Secret key and API key
53
+ #
54
+ # @param api_uri [String] HTTP URL of the CloudStack API
55
+ # @param api_key [String] CloudStack API Key
56
+ # @param secret_key [String] Cloudstack Secret Key
57
+ # @param http_method [Symbol] The HTTP method to submit the query (:post or :get)
58
+ # @param ssl_check [Boolean] Check Server SSL certificate?
59
+ # @return [Boolean]
60
+ def self.verify_keys(api_uri, api_key, secret_key, http_method = nil, ssl_check = nil)
61
+ http_method ||= API_DEFAULTS[:http_method]
62
+ ssl_check ||= API_DEFAULTS[:ssl_check]
63
+ CloudStackClient::API::response_is_success? query(api_uri, {:command => "listCapabilities"}, {:api_key => api_key, :secret_key => secret_key}, http_method, ssl_check)
64
+ end
65
+
66
+ # Execute a query
67
+ #
68
+ # @param api_uri [String] HTTP URL of the CloudStack API
69
+ # @param parameters [Hash] A Hash containing all query parameters. The keys can be specified as Symbol or String
70
+ # @param credentials [Hash] Contains either :api_key and :secret_key or :session_key and :session_cookies as obtained from {get_credentials}
71
+ # @param http_method [Symbol] The HTTP method to submit the query (:post or :get)
72
+ # @param ssl_check [Boolean] Check Server SSL certificate?
73
+ # @return [REXML::Document, Hash] An XML Document or the parsed JSON (commonly a Hash)
74
+ def self.query(api_uri, parameters, credentials, http_method = nil, ssl_check = nil)
75
+ http_method ||= API_DEFAULTS[:http_method]
76
+ ssl_check ||= API_DEFAULTS[:ssl_check]
77
+ if credentials.is_a?(Hash) && credentials.has_key?(:api_key) && credentials.has_key?(:secret_key)
78
+ parameters[:apiKey] = credentials[:api_key]
79
+ parameters[:secret] = credentials[:secret_key]
80
+ parameters[:timestamp] = Time.new.to_i
81
+ headers, body = self.execute_query(api_uri, generate_query(parameters), [], http_method, ssl_check)
82
+ elsif credentials.is_a? Hash
83
+ parameters[:sessionkey] = credentials[:session_key] if credentials.has_key? :session_key
84
+ headers, body = self.execute_query(api_uri, generate_query(parameters), credentials[:session_cookies], http_method, ssl_check)
85
+ end
86
+
87
+ format = nil
88
+ if headers["content-type"]
89
+ format = headers["content-type"][0].split(";").first
90
+ end
91
+
92
+ case format
93
+ when "text/javascript"
94
+ JSON.parse body
95
+ when "text/xml"
96
+ REXML::Document.new body
97
+ else
98
+ body
99
+ end
100
+ end
101
+
102
+ # Executes the query like query() but merges all result pages into one
103
+ #
104
+ # @param api_uri [String] HTTP URL of the CloudStack API
105
+ # @param parameters [Hash] A Hash containing all query parameters. The keys can be specified as Symbol or String
106
+ # @param credentials [Hash] Contains either :api_key and :secret_key or :session_key and :session_cookies as obtained from {get_credentials}
107
+ # @param http_method [Symbol] The HTTP method to submit the query (:post or :get)
108
+ # @param ssl_check [Boolean] Check Server SSL certificate?
109
+ # @return [REXML::Document, Hash] An XML Document or the parsed JSON (commonly a Hash)
110
+ def self.query_with_auto_paging(api_uri, parameters, credentials, page_size = nil, http_method = nil, ssl_check = nil)
111
+ page_size ||= API_DEFAULTS[:page_size]
112
+ http_method ||= API_DEFAULTS[:http_method]
113
+ ssl_check ||= API_DEFAULTS[:ssl_check]
114
+ output = nil
115
+ parameters[:page] = 0
116
+ parameters[:pageSize] = page_size
117
+ has_count = false
118
+
119
+ begin
120
+ parameters[:page] += 1
121
+ data = self.query api_uri, parameters, credentials, http_method, ssl_check
122
+ return nil unless data
123
+ format = :json
124
+ format = :xml if data.is_a? REXML::Document
125
+
126
+ # Remove count attribute from result data
127
+ if format == :xml
128
+ count_data = REXML::QuickPath.first(data.root, "//count")
129
+ if count_data
130
+ count = count_data.text.to_i
131
+ count_data.remove
132
+ has_count = true
133
+ else
134
+ count = 0
135
+ end
136
+ elsif format == :json
137
+ if data.first[1].has_key? "count"
138
+ count = data.first[1]["count"]
139
+ data.first[1].delete "count"
140
+ has_count = true
141
+ else
142
+ count = 0
143
+ end
144
+ end
145
+
146
+ # Copy first page in total
147
+ if !output
148
+ output = data
149
+ loop_done = count < parameters[:pageSize]
150
+ next
151
+ end
152
+
153
+ if count <= 0
154
+ # No elements
155
+ loop_done = true
156
+ elsif count >= parameters[:pageSize]
157
+ # Copy all elements if page is full
158
+ if format == :xml
159
+ data.root.elements.each {|record| output.root << record}
160
+ elsif format == :json
161
+ data.first[1].first[1].each {|record| output.first[1].first[1] << record}
162
+ end
163
+ else
164
+ # Handle last page
165
+ loop_done = true
166
+ # Detect and ignore duplicates that have already been extracted.
167
+ # E.g. guest-utilities during listIsos command
168
+ if format == :xml
169
+ pos_output = output.root.elements.size - count + 1
170
+ end_output = output.root.elements.size + 1
171
+ while pos_output < end_output
172
+ if output.root.elements[pos_output].to_s != data.root.elements[1].to_s
173
+ output.root << data.root.elements[1] # Move node to output tree
174
+ end
175
+ pos_output += 1
176
+ end
177
+ elsif format == :json
178
+ pos_data = 0
179
+ pos_output = output.first[1].first[1].size - count
180
+ end_output = output.first[1].first[1].size
181
+ while pos_output < end_output
182
+ if output.first[1].first[1][pos_output] != data.first[1].first[1][pos_data]
183
+ output.first[1].first[1] << data.first[1].first[1][pos_data]
184
+ end
185
+ pos_data += 1
186
+ pos_output += 1
187
+ end
188
+ end
189
+ end
190
+ end until loop_done
191
+
192
+ # Recalculate count attribute
193
+ if has_count && CloudStackClient::API::response_is_success?(output)
194
+ if format == :xml && output.root && output.root.has_elements?
195
+ new_count = REXML::Element.new "count"
196
+ new_count.add_text output.root.elements.count.to_s
197
+ output.root << new_count
198
+ elsif format == :json && output.first && output.first[1].any?
199
+ output.first[1]["count"] = output.first[1].first[1].count
200
+ end
201
+ end
202
+ output
203
+ end
204
+
205
+ # Generate a single sign on link for the management console
206
+ #
207
+ # @param web_uri [String] The base URL of Webinterface
208
+ # @param username [String] CloudStack login user
209
+ # @param hashed_password [String] CloudStack login password (use {CloudStackClient::Helpers::hash_password})
210
+ # @param domain [String] CloudStack login domain
211
+ # @param login_parameter [String] The name of the CloudStack URL parameter that has been specified in scripts/cloud.core.callbacks.js to contain the login credentials
212
+ # @param timestamp_offset [Fixnum] Custom scripts/cloud.core.callbacks.js parameter to invalidate the login link after the specified number of seconds
213
+ # @return [String] A complete URL
214
+ # @see CloudStackClient::Helpers::hash_password
215
+ def self.generate_management_console_link(web_uri, username, hashed_password, domain = "", login_parameter = nil, timestamp_offset = nil)
216
+ login_parameter ||= API_DEFAULTS[:login_parameter]
217
+ timestamp_offset ||= API_DEFAULTS[:timestamp_offset]
218
+ uri = "#{web_uri}?#{login_parameter}="
219
+ uri += escape(generate_query({:command => "login", :response => "json", :username => username, :password => hashed_password, :domain => parse_domain(domain)}))
220
+ uri += "&timestamp=#{Time.new.to_i + timestamp_offset}" if timestamp_offset
221
+ return uri
222
+ end
223
+
224
+ private
225
+
226
+ # Sanitize the domain
227
+ def self.parse_domain(domain = "")
228
+ domain = domain.to_s
229
+ if domain.start_with? "/"
230
+ domain
231
+ else
232
+ "/" + domain
233
+ end
234
+ end
235
+
236
+ # Make string URL safe
237
+ def self.escape(string)
238
+ URI.escape(string, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
239
+ end
240
+
241
+ # Construct a query from a Hash of parameters or an Array of [key, value] Arrays
242
+ def self.generate_query(parameters = {})
243
+ if parameters.is_a? Enumerable
244
+ # Escape the key / value pairs
245
+ parameters = parameters.map {|param| [escape(param[0].to_s), escape(param[1].to_s)]}
246
+ else
247
+ parameters = [[escape(parameters.to_s), ""]]
248
+ end
249
+ secret = nil
250
+ parameters.delete_if {|key, value| secret = value if key == "secret"}
251
+
252
+ # Sign the query if secret is available
253
+ if secret
254
+ # Sort the parameters
255
+ parameters.sort!
256
+ # Generate query string
257
+ query = parameters.map {|param| param.join "="}.join("&")
258
+ # Generate signature by calculating SHA1 HMAC
259
+ hmac = OpenSSL::HMAC.digest("sha1", secret, query.downcase)
260
+ # Base64 encode 8 bit data and discard newline
261
+ base64 = Base64.encode64(hmac).strip
262
+ # Make signature URL safe
263
+ signature = escape(base64)
264
+ # Append signature to query
265
+ query += "&signature=" + signature
266
+ else
267
+ query = parameters.map {|param| param.join "="}.join("&")
268
+ end
269
+ query
270
+ end
271
+
272
+ # Submit the query to the server
273
+ def self.execute_query(api_uri, query, cookies = [], http_method = API_DEFAULTS[:http_method], ssl_check = API_DEFAULTS[:ssl_check])
274
+ uri = URI.parse api_uri
275
+ http = Net::HTTP.new uri.host, uri.port
276
+ if uri.scheme == "https"
277
+ http.use_ssl = true
278
+ # Verify server SSL certificate
279
+ if ssl_check
280
+ # On Ruby before 1.9 a local CA store is needed to verify SSL certificates.
281
+ ruby_version = RUBY_VERSION.split(".").map{|num| num.to_i}
282
+ if ruby_version[0] == 1 && ruby_version[1] < 9 || ruby_version[0] < 1
283
+ http.ca_file = File.join(File.dirname(__FILE__), "../../assets/cacert.pem")
284
+ end
285
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
286
+ end
287
+ end
288
+ request_cookies = nil
289
+ request_cookies = cookies.join(';') if cookies.is_a?(Array) && cookies.any?
290
+ if http_method == :post
291
+ request = Net::HTTP::Post.new uri.path
292
+ request["Cookie"] = request_cookies if request_cookies
293
+ response = http.request request, query
294
+ elsif http_method == :get
295
+ request = Net::HTTP::Get.new "#{uri.path}?#{query}"
296
+ request["Cookie"] = request_cookies if request_cookies
297
+ response = http.request request
298
+ else
299
+ raise "Only HTTP methods :post and :get are implemented."
300
+ end
301
+ return response.to_hash, response.body # Hash of HTTP Response header and HTTP Body
302
+ end
303
+
304
+ # Returns the format of a given reponse. Eigther :xml or :json
305
+ def self.response_format(response)
306
+ if response.is_a?(REXML::Document)
307
+ :xml
308
+ else
309
+ :json
310
+ end
311
+ end
312
+
313
+ # Checks the given response whether the command was successful
314
+ def self.response_is_success?(response)
315
+ if response.is_a? REXML::Document
316
+ !(response.root.elements["errorcode"] || response.root.name == "errorresponse")
317
+ elsif response.is_a?(Hash) && response.any?
318
+ !(response.first[1]["errorcode"] || response.first[0] == "errorresponse")
319
+ else
320
+ false
321
+ end
322
+ end
323
+
324
+ end
325
+ end
@@ -0,0 +1,239 @@
1
+ require "cloud_stack_client"
2
+ require "yaml"
3
+
4
+ module CloudStackClient
5
+ # The commandline client 'cloud-stack.rb'
6
+ #
7
+ # Plesae run +cloud-stack.rb --help+ for additional info.
8
+ class CLI
9
+ # Default options for the command line. Can be overridden by parameters and a config file
10
+ DEFAULT_OPTIONS = {
11
+ :api_uri => nil,
12
+ :api_key => nil,
13
+ :secret_key => nil,
14
+ :username => nil,
15
+ :password => nil,
16
+ :domain => nil,
17
+ :response => "json",
18
+ :output => nil,
19
+ :debug => false,
20
+ :auto_paging => true,
21
+ :page_size => 500
22
+ }.freeze
23
+
24
+ # Create new commandline instance
25
+ def initialize
26
+ @options = DEFAULT_OPTIONS.dup
27
+ @parameters = {}
28
+ end
29
+
30
+ # Execute the command
31
+ #
32
+ # @param command [String] The name of the executable ($0)
33
+ # @param arguments [Array] Commandline parameters (ARGV)
34
+ # @return [Boolean] true if successful
35
+ def run(command, arguments)
36
+ # Parse commandline
37
+ pos = arguments.find_index("--config")
38
+ if pos
39
+ arguments.delete_at pos
40
+ raise "Path to config file missing" unless arguments[pos]
41
+ filename = arguments.delete_at pos
42
+ yaml = YAML.load_file filename
43
+ yaml["default"].each {|key, value| @options[key.to_sym] = value}
44
+ end
45
+ if arguments.empty?
46
+ print_help File.basename(command), DEFAULT_OPTIONS
47
+ return false
48
+ end
49
+ while arguments.any? do
50
+ arg = arguments.shift
51
+ raise "Unknown parameter #{arg}" unless arg.start_with? '--'
52
+ key = arg.slice(2, arg.length - 2)
53
+ case key
54
+ when 'debug', 'ignore-ssl', 'auto-paging', 'no-auto-paging'
55
+ # Single parameter options
56
+ if key.start_with? 'no-'
57
+ key = key.slice(3, arg.length - 3)
58
+ @options[key.gsub('-', '_').to_sym] = false
59
+ else
60
+ @options[key.gsub('-', '_').to_sym] = true
61
+ end
62
+ when 'api-key', 'secret-key', 'username', 'password', 'domain', 'api-uri', 'response', 'output', 'pagesize', 'config'
63
+ # Second parameter options
64
+ raise "Missing value for #{arg}" if arguments.empty?
65
+ value = arguments.shift
66
+ if key == 'pagesize'
67
+ value = value.to_i
68
+ end
69
+ @options[key.gsub('-', '_').to_sym] = value
70
+ when 'help'
71
+ print_help File.basename(command), DEFAULT_OPTIONS
72
+ return false
73
+ when 'config-stub'
74
+ print_config DEFAULT_OPTIONS
75
+ return false
76
+ else
77
+ raise "Missing value for #{arg}" if arguments.empty?
78
+ @parameters[key] = arguments.shift
79
+ end
80
+ end
81
+
82
+ # Check existence of base URL
83
+ unless @options[:api_uri] && !@options[:api_uri].empty?
84
+ raise "Server API URL is missing. Specify with --api-uri parameter."
85
+ end
86
+
87
+ # Check existence of a command
88
+ unless @parameters['command']
89
+ warn "No command specified. Use --command COMMAND to specify a remote command."
90
+ end
91
+
92
+ # Check that auto paging is only used on list* commands
93
+ unless @parameters['command'] && @parameters['command'].downcase.start_with?("list")
94
+ @options[:auto_paging] = false
95
+ warn "Auto Paging disabled because command does not start with 'list'." if @options[:debug]
96
+ end
97
+
98
+ # Set response format if specified
99
+ if @options[:response]
100
+ warn "Unknown response format #{@options[:response]}." unless ['xml', 'json'].include? @options[:response]
101
+ @parameters['response'] = @options[:response]
102
+ end
103
+
104
+ # Login to CloudStack API
105
+ api = CloudStackClient::Connector.new({:api_uri => @options[:api_uri], :page_size => @options[:page_size], :ssl_check => !@options[:ignore_ssl]})
106
+ if @options[:api_key] && @options[:secret_key]
107
+ unless api.login_with_keys @options[:api_key], @options[:secret_key]
108
+ raise "Login using API key and secret key (--api-key KEY --secret-key SECRET) failed."
109
+ end
110
+ elsif @options[:username] && @options[:password]
111
+ unless api.login @options[:username], @options[:password], @options[:domain]
112
+ raise "Login using username and password failed."
113
+ end
114
+ else
115
+ warn "No credentials specified." if @options[:debug]
116
+ end
117
+
118
+ # Execute query
119
+ warn "Parameters:\n#{@parameters.inspect}\n" if @options[:debug]
120
+ if @options[:auto_paging]
121
+ output = api.query_all_pages @parameters
122
+ else
123
+ output = api.query @parameters
124
+ end
125
+
126
+ # Verify result
127
+ errorcode = nil
128
+ message = "Unrecognizable Result"
129
+ unless CloudStackClient::API::response_is_success? output
130
+ case CloudStackClient::API::response_format(output)
131
+ when :xml
132
+ errorcode = REXML::XPath.first(output.root, "//errorcode").text.to_i
133
+ message = REXML::XPath.first(output.root, "//errortext").text
134
+ when :json
135
+ errorcode = output.first[1]["errorcode"]
136
+ message = output.first[1]["errortext"]
137
+ else
138
+ message = "Unknown format #{CloudStackClient::API::response_format(output)}"
139
+ end
140
+ warn "Error #{errorcode} occured: #{message}\n"
141
+ end
142
+
143
+ # Output result
144
+ if not @options[:output]
145
+ # If no output is specified show formatted result on STDOUT
146
+ case CloudStackClient::API::response_format(output)
147
+ when :xml
148
+ puts "XML Response:"
149
+ puts output
150
+ when :json
151
+ puts "JSON Response:"
152
+ puts output.to_yaml
153
+ else
154
+ warn "Unknown format #{CloudStackClient::API::response_format(output)}" if @options[:debug]
155
+ end
156
+ elsif @options[:output] == '-'
157
+ # Pipe to STDOUT
158
+ STDOUT << output
159
+ puts # Fix missing newline
160
+ else
161
+ # Write to file
162
+ file = File.new(@options[:output], 'w')
163
+ file << output
164
+ file.close
165
+ end
166
+
167
+ !!errorcode
168
+ end
169
+
170
+ private
171
+
172
+ # Show help
173
+ def print_help(command, options)
174
+ puts "CloudStack API Commandline Interface (version #{CloudStackClient::VERSION})"
175
+ puts "Usage: #{command} [options]"
176
+ puts "Options:"
177
+ puts "\t--api-key api-key Your unique API key"
178
+ puts "\t--secret-key secret-key Your secret signature key"
179
+ puts "\t--username Your login username"
180
+ puts "\t--password Your login password"
181
+ puts "\t--domain Your login domain"
182
+ puts "\t--config yaml-file A YAML configuration file"
183
+ puts "\t--api-uri url The URL of the API. Example:"
184
+ puts "\t https://example.com/client/api"
185
+ puts "\t--response format Specify eighter json or xml (default: #{options[:response]})"
186
+ puts "\t--output filename Execute command and save the response"
187
+ puts "\t--[no-]auto-paging Aggregate all output pages (default: #{options[:auto_paging] ? "on" : "off"})"
188
+ puts "\t--pagesize num Server's maximum page size (default: #{options[:page_size]})"
189
+ puts "\t--ignore-ssl Ignore SSL certificate errors"
190
+ puts "\t--debug Additional output"
191
+ puts "\t--config-stub Prints a skeleton for the configuration file"
192
+ puts "\t--help This help"
193
+ puts
194
+ puts "Any API parameter can be passed as additional parameter:"
195
+ puts "\t--parameter value Adds 'parameter=value' to the query"
196
+ puts
197
+ puts "You can authenticate wit secret and API keys or using username, password and domain."
198
+ puts
199
+ puts "Examples:"
200
+ puts "#{command} --api-uri https://example.com/client/api \\"
201
+ puts "\t--api-key mYaPiKeY --secret-key MySeCrEt --command listTemplates \\"
202
+ puts "\t--templatefilter executable"
203
+ puts "#{command} --api-uri http://example.com/api --username Me --password \\"
204
+ puts "\tMyPass --domain My/Domain --command deployVirtualMachine --zoneid 123 \\"
205
+ puts "\t--serviceofferingid 234 --templateid 456"
206
+ puts "#{command} --config credentials.yml --command queryAsyncJobResult \\"
207
+ puts "\t--jobid 567"
208
+ puts "#{command} --config credentials.yml --command createPortForwardingRule \\"
209
+ puts "\t--ipaddressid 678 --protocol TCP --publicport 443 --privateport 443 \\"
210
+ puts "\t--virtualmachineid 789"
211
+ end
212
+
213
+ # Print config file skeleton
214
+ def print_config(options)
215
+ puts <<-YAML
216
+ # Specify default options for cloud-stack.rb.
217
+ #
218
+ # CloudStack Authentiction can eighter be API key and Secret key or username,
219
+ # password and domain.
220
+ #
221
+ # api_uri is the URL of the API e.g. https://www.example.com/client/api
222
+ #
223
+ # The supported response formats are json and xml.
224
+
225
+ default:
226
+ api_uri:
227
+ api_key:
228
+ secret_key:
229
+ username:
230
+ password:
231
+ domain:
232
+ # response: xml
233
+ # debug: true
234
+ # auto_paging: false
235
+ # page_size: 100
236
+ YAML
237
+ end
238
+ end
239
+ end