knife-cloudstack 0.0.14 → 0.0.15
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.
- data/CHANGES.rdoc +15 -0
- data/README.rdoc +18 -7
- data/lib/chef/knife/cs_base.rb +12 -8
- data/lib/chef/knife/cs_firewallrule_create.rb +115 -0
- data/lib/chef/knife/cs_forwardrule_create.rb +113 -0
- data/lib/chef/knife/cs_hosts.rb +7 -32
- data/lib/chef/knife/cs_keypair_create.rb +72 -0
- data/lib/chef/knife/cs_keypair_delete.rb +60 -0
- data/lib/chef/knife/cs_keypair_list.rb +83 -0
- data/lib/chef/knife/cs_publicip_list.rb +88 -0
- data/lib/chef/knife/cs_securitygroup_list.rb +134 -0
- data/lib/chef/knife/cs_server_create.rb +66 -15
- data/lib/chef/knife/cs_server_list.rb +4 -2
- data/lib/chef/knife/cs_stack_create.rb +17 -43
- data/lib/chef/knife/cs_template_list.rb +1 -1
- data/lib/chef/knife/cs_volume_create.rb +108 -0
- data/lib/knife-cloudstack/connection.rb +243 -100
- metadata +85 -86
- data/lib/knife-cloudstack/string_to_regexp.rb +0 -32
@@ -85,7 +85,9 @@ module KnifeCloudstack
|
|
85
85
|
|
86
86
|
output_format(connection_result)
|
87
87
|
|
88
|
-
rules = connection.list_port_forwarding_rules
|
88
|
+
rules = connection.list_port_forwarding_rules(nil, true)
|
89
|
+
public_list = connection.list_public_ip_addresses(true)
|
90
|
+
|
89
91
|
|
90
92
|
connection_result.each do |r|
|
91
93
|
name = r['name']
|
@@ -98,7 +100,7 @@ module KnifeCloudstack
|
|
98
100
|
locate_config_value(:fields).downcase.split(',').each { |n| object_list << ((r[("#{n}").strip]).to_s || 'N/A') }
|
99
101
|
else
|
100
102
|
object_list << r['name']
|
101
|
-
object_list << (connection.get_server_public_ip(r, rules) || '')
|
103
|
+
r['nic'].empty? ? object_list << "N/A" : object_list << (connection.get_server_public_ip(r, rules, public_list) || '')
|
102
104
|
object_list << r['serviceofferingname']
|
103
105
|
object_list << r['templatename']
|
104
106
|
object_list << r['state']
|
@@ -16,11 +16,17 @@
|
|
16
16
|
# limitations under the License.
|
17
17
|
#
|
18
18
|
|
19
|
+
require 'chef/knife/cs_base'
|
20
|
+
require 'chef/knife/cs_baselist'
|
21
|
+
|
19
22
|
module KnifeCloudstack
|
20
23
|
class CsStackCreate < Chef::Knife
|
21
24
|
|
22
25
|
attr_accessor :current_stack
|
23
26
|
|
27
|
+
include Chef::Knife::KnifeCloudstackBase
|
28
|
+
include Chef::Knife::KnifeCloudstackBaseList
|
29
|
+
|
24
30
|
deps do
|
25
31
|
require 'chef/json_compat'
|
26
32
|
require 'chef/mash'
|
@@ -29,7 +35,6 @@ module KnifeCloudstack
|
|
29
35
|
require 'net/ssh'
|
30
36
|
require 'net/ssh/multi'
|
31
37
|
require 'knife-cloudstack/connection'
|
32
|
-
require 'chef/knife'
|
33
38
|
Chef::Knife.load_deps
|
34
39
|
Chef::Knife::Ssh.load_deps
|
35
40
|
Chef::Knife::NodeRunListRemove.load_deps
|
@@ -38,24 +43,6 @@ module KnifeCloudstack
|
|
38
43
|
|
39
44
|
banner "knife cs stack create JSON_FILE (options)"
|
40
45
|
|
41
|
-
option :cloudstack_url,
|
42
|
-
:short => "-U URL",
|
43
|
-
:long => "--cloudstack-url URL",
|
44
|
-
:description => "The CloudStack endpoint URL",
|
45
|
-
:proc => Proc.new { |url| Chef::Config[:knife][:cloudstack_url] = url }
|
46
|
-
|
47
|
-
option :cloudstack_api_key,
|
48
|
-
:short => "-A KEY",
|
49
|
-
:long => "--cloudstack-api-key KEY",
|
50
|
-
:description => "Your CloudStack API key",
|
51
|
-
:proc => Proc.new { |key| Chef::Config[:knife][:cloudstack_api_key] = key }
|
52
|
-
|
53
|
-
option :cloudstack_secret_key,
|
54
|
-
:short => "-K SECRET",
|
55
|
-
:long => "--cloudstack-secret-key SECRET",
|
56
|
-
:description => "Your CloudStack secret key",
|
57
|
-
:proc => Proc.new { |key| Chef::Config[:knife][:cloudstack_secret_key] = key }
|
58
|
-
|
59
46
|
option :ssh_user,
|
60
47
|
:short => "-x USERNAME",
|
61
48
|
:long => "--ssh-user USERNAME",
|
@@ -72,6 +59,8 @@ module KnifeCloudstack
|
|
72
59
|
:description => "The SSH identity file used for authentication"
|
73
60
|
|
74
61
|
def run
|
62
|
+
validate_base_options
|
63
|
+
|
75
64
|
file_path = File.expand_path(@name_args.first)
|
76
65
|
unless File.exist?(file_path) then
|
77
66
|
ui.error "Stack file '#{file_path}' not found. Please check the path."
|
@@ -81,25 +70,12 @@ module KnifeCloudstack
|
|
81
70
|
data = File.read file_path
|
82
71
|
stack = Chef::JSONCompat.from_json data
|
83
72
|
create_stack stack
|
84
|
-
|
85
|
-
#puts "Stack: #{stack.inspect}"
|
86
|
-
end
|
87
|
-
|
88
|
-
def connection
|
89
|
-
if (!@connection) then
|
90
|
-
url = locate_config_value(:cloudstack_url)
|
91
|
-
api_key = locate_config_value(:cloudstack_api_key)
|
92
|
-
secret_key = locate_config_value(:cloudstack_secret_key)
|
93
|
-
@connection = CloudstackClient::Connection.new(url, api_key, secret_key)
|
94
|
-
end
|
95
|
-
@connection
|
96
73
|
end
|
97
74
|
|
98
75
|
def create_stack(stack)
|
99
76
|
@current_stack = Mash.new(stack)
|
100
77
|
current_stack[:servers].each do |server|
|
101
78
|
if server[:name]
|
102
|
-
|
103
79
|
# create server(s)
|
104
80
|
names = server[:name].split(/[\s,]+/)
|
105
81
|
names.each do |n|
|
@@ -120,16 +96,21 @@ module KnifeCloudstack
|
|
120
96
|
def create_server(server)
|
121
97
|
|
122
98
|
cmd = KnifeCloudstack::CsServerCreate.new([server[:name]])
|
123
|
-
|
124
99
|
# configure and run command
|
125
100
|
# TODO: validate parameters
|
126
101
|
cmd.config[:ssh_user] = config[:ssh_user]
|
127
102
|
cmd.config[:ssh_password] = config[:ssh_password]
|
128
|
-
cmd.config[:ssh_port] = Chef::Config[:knife][:ssh_port]
|
103
|
+
cmd.config[:ssh_port] = config[:ssh_port] || "22" # Chef::Config[:knife][:ssh_port]
|
129
104
|
cmd.config[:identity_file] = config[:identity_file]
|
130
105
|
cmd.config[:cloudstack_template] = server[:template] if server[:template]
|
131
106
|
cmd.config[:cloudstack_service] = server[:service] if server[:service]
|
132
|
-
cmd.config[:cloudstack_zone] = server[:
|
107
|
+
cmd.config[:cloudstack_zone] = server[:zone] if server[:zone]
|
108
|
+
server.has_key?(:public_ip) ? cmd.config[:public_ip] = server[:public_ip] : cmd.config[:no_public_ip] = true
|
109
|
+
cmd.config[:bootstrap] = server[:bootstrap] if server.has_key?(:bootstrap)
|
110
|
+
cmd.config[:bootstrap_protocol] = server[:bootstrap_protocol] || "ssh"
|
111
|
+
cmd.config[:distro] = server[:distro] || "chef-full"
|
112
|
+
cmd.config[:template_file] = server[:template_file] if server.has_key?(:template_file)
|
113
|
+
cmd.config[:no_host_key_verify] = server[:no_host_key_verify] if server.has_key?(:no_host_key_verify)
|
133
114
|
cmd.config[:cloudstack_networks] = server[:networks].split(/[\s,]+/) if server[:networks]
|
134
115
|
cmd.config[:run_list] = server[:run_list].split(/[\s,]+/) if server[:run_list]
|
135
116
|
cmd.config[:port_rules] = server[:port_rules].split(/[\s,]+/) if server[:port_rules]
|
@@ -139,7 +120,6 @@ module KnifeCloudstack
|
|
139
120
|
end
|
140
121
|
|
141
122
|
cmd.run_with_pretty_exceptions
|
142
|
-
|
143
123
|
end
|
144
124
|
|
145
125
|
def run_actions(actions)
|
@@ -171,7 +151,7 @@ module KnifeCloudstack
|
|
171
151
|
query = "(#{query})" + " AND chef_environment:#{get_environment}"
|
172
152
|
end
|
173
153
|
|
174
|
-
Chef::
|
154
|
+
Chef::Log.debug("Searching for nodes: #{query}")
|
175
155
|
|
176
156
|
q = Chef::Search::Query.new
|
177
157
|
nodes = Array(q.search(:node, query))
|
@@ -315,11 +295,5 @@ module KnifeCloudstack
|
|
315
295
|
puts hosts.join("\n")
|
316
296
|
end
|
317
297
|
end
|
318
|
-
|
319
|
-
def locate_config_value(key)
|
320
|
-
key = key.to_sym
|
321
|
-
Chef::Config[:knife][key] || config[key]
|
322
|
-
end
|
323
|
-
|
324
298
|
end
|
325
299
|
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Jeremy Baumont (<jbaumont@schubergphilis.com>)
|
3
|
+
# Copyright:: Copyright (c) Schuberg Philis 2013
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'chef/knife/cs_base'
|
20
|
+
|
21
|
+
module KnifeCloudstack
|
22
|
+
class CsVolumeCreate < Chef::Knife
|
23
|
+
|
24
|
+
include Chef::Knife::KnifeCloudstackBase
|
25
|
+
|
26
|
+
deps do
|
27
|
+
require 'knife-cloudstack/connection'
|
28
|
+
Chef::Knife.load_deps
|
29
|
+
end
|
30
|
+
|
31
|
+
banner "knife cs volume create NAME (options)"
|
32
|
+
|
33
|
+
option :name,
|
34
|
+
:long => "--name NAME",
|
35
|
+
:description => "The name of the disk volume.",
|
36
|
+
:required => true,
|
37
|
+
:on => :head
|
38
|
+
|
39
|
+
option :account,
|
40
|
+
:long => "--account ACCOUNT_NAME",
|
41
|
+
:description => "The account associated with the disk volume. Must be used with the domainId parameter."
|
42
|
+
|
43
|
+
option :diskofferingid,
|
44
|
+
:long => "--diskofferingid ID",
|
45
|
+
:description => "The ID of the disk offering. Either diskOfferingId or snapshotId must be passed in."
|
46
|
+
|
47
|
+
option :domainid,
|
48
|
+
:long => "--domainid ID",
|
49
|
+
:description => "The domain ID associated with the disk offering. If used with the account parameter returns the disk volume associated with the account for the specified domain."
|
50
|
+
|
51
|
+
option :size,
|
52
|
+
:long => "--size SIZE",
|
53
|
+
:description => "Arbitrary volume size."
|
54
|
+
|
55
|
+
option :snapshotid,
|
56
|
+
:long => "--snapshotid ID",
|
57
|
+
:description => "The snapshot ID for the disk volume. Either diskOfferingId or snapshotId must be passed in."
|
58
|
+
|
59
|
+
option :zoneid,
|
60
|
+
:long => "--zoneid ID",
|
61
|
+
:description => "The ID of the availability zone.",
|
62
|
+
:required => true,
|
63
|
+
:on => :head
|
64
|
+
|
65
|
+
|
66
|
+
def run
|
67
|
+
validate_base_options
|
68
|
+
|
69
|
+
Chef::Log.debug("Validate hostname and options")
|
70
|
+
if locate_config_value(:name)
|
71
|
+
volumename = locate_config_value(:name)
|
72
|
+
else
|
73
|
+
volumename = @name_args.first
|
74
|
+
unless /^[a-zA-Z0-9][a-zA-Z0-9_\-#]*$/.match volumename then
|
75
|
+
ui.error "Invalid volumename. Please specify a simple name without any spaces"
|
76
|
+
exit 1
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
print "#{ui.color("Creating volume: #{volumename}", :magenta)}\n"
|
81
|
+
|
82
|
+
params = {
|
83
|
+
'command' => 'createVolume',
|
84
|
+
'name' => volumename,
|
85
|
+
}
|
86
|
+
|
87
|
+
params['account'] = locate_config_value(:account) if locate_config_value(:account)
|
88
|
+
params['diskofferingid'] = locate_config_value(:diskofferingid) if locate_config_value(:diskofferingid)
|
89
|
+
params['domainid'] = locate_config_value(:domainid) if locate_config_value(:domainid)
|
90
|
+
params['projectid'] = locate_config_value(:projectid) if locate_config_value(:projectid)
|
91
|
+
params['size'] = locate_config_value(:size) if locate_config_value(:size)
|
92
|
+
params['snapshotid'] = locate_config_value(:snapshotid) if locate_config_value(:snapshotid)
|
93
|
+
params['zoneid'] = locate_config_value(:zoneid) if locate_config_value(:zoneid)
|
94
|
+
|
95
|
+
json = connection.send_request(params)
|
96
|
+
|
97
|
+
if ! json then
|
98
|
+
ui.error("Unable to create volume")
|
99
|
+
exit 1
|
100
|
+
end
|
101
|
+
|
102
|
+
print "Volume #{json['id']} is being created in the background\n";
|
103
|
+
|
104
|
+
return json['id']
|
105
|
+
end
|
106
|
+
|
107
|
+
end # class
|
108
|
+
end
|
@@ -3,6 +3,7 @@
|
|
3
3
|
# Author:: KC Braunschweig (<kcbraunschweig@gmail.com>)
|
4
4
|
# Author:: Sander Botman (<sbotman@schubergphilis.com>)
|
5
5
|
# Author:: Frank Breedijk (<fbreedijk@schubergphilis.com>)
|
6
|
+
# Author:: Sander van Harmelen (<svanharmelen@schubergphilis.com>)
|
6
7
|
# Copyright:: Copyright (c) 2011 Edmunds, Inc.
|
7
8
|
# License:: Apache License, Version 2.0
|
8
9
|
#
|
@@ -27,7 +28,24 @@ require 'cgi'
|
|
27
28
|
require 'net/http'
|
28
29
|
require 'json'
|
29
30
|
require 'highline/import'
|
30
|
-
|
31
|
+
|
32
|
+
class String
|
33
|
+
def to_regexp
|
34
|
+
return nil unless self.strip.match(/\A\/(.*)\/(.*)\Z/mx)
|
35
|
+
regexp , flags = $1 , $2
|
36
|
+
return nil if !regexp || flags =~ /[^xim]/m
|
37
|
+
|
38
|
+
x = /x/.match(flags) && Regexp::EXTENDED
|
39
|
+
i = /i/.match(flags) && Regexp::IGNORECASE
|
40
|
+
m = /m/.match(flags) && Regexp::MULTILINE
|
41
|
+
|
42
|
+
Regexp.new regexp , [x,i,m].inject(0){|a,f| f ? a+f : a }
|
43
|
+
end
|
44
|
+
|
45
|
+
def is_uuid?
|
46
|
+
self.strip =~ /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/ ? true : false
|
47
|
+
end
|
48
|
+
end
|
31
49
|
|
32
50
|
module CloudstackClient
|
33
51
|
class Connection
|
@@ -35,23 +53,26 @@ module CloudstackClient
|
|
35
53
|
ASYNC_POLL_INTERVAL = 5.0
|
36
54
|
ASYNC_TIMEOUT = 600
|
37
55
|
|
38
|
-
def initialize(api_url, api_key, secret_key, project_name=nil,
|
56
|
+
def initialize(api_url, api_key, secret_key, project_name=nil, no_ssl_verify=false, api_proxy=nil)
|
39
57
|
@api_url = api_url
|
40
58
|
@api_key = api_key
|
41
59
|
@secret_key = secret_key
|
42
|
-
@
|
43
|
-
@
|
44
|
-
|
45
|
-
project = get_project(project_name)
|
46
|
-
if !project then
|
47
|
-
puts "Project #{project_name} does not exist"
|
48
|
-
exit 1
|
49
|
-
end
|
50
|
-
@project_id = project['id']
|
51
|
-
end
|
52
|
-
|
60
|
+
@no_ssl_verify = no_ssl_verify
|
61
|
+
@project_id = get_project_id(project_name) if project_name || nil
|
62
|
+
@api_proxy = api_proxy
|
53
63
|
end
|
54
64
|
|
65
|
+
##
|
66
|
+
# Get project id
|
67
|
+
def get_project_id(name)
|
68
|
+
project = get_project(name)
|
69
|
+
if !project then
|
70
|
+
puts "Project #{project_name} does not exist"
|
71
|
+
exit 1
|
72
|
+
end
|
73
|
+
project['id']
|
74
|
+
end
|
75
|
+
|
55
76
|
##
|
56
77
|
# Finds the server with the specified name.
|
57
78
|
|
@@ -76,16 +97,23 @@ module CloudstackClient
|
|
76
97
|
##
|
77
98
|
# Finds the public ip for a server
|
78
99
|
|
79
|
-
def get_server_public_ip(server, cached_rules=nil)
|
100
|
+
def get_server_public_ip(server, cached_rules=nil, cached_nat=nil)
|
80
101
|
return nil unless server
|
81
102
|
# find the public ip
|
82
103
|
nic = get_server_default_nic(server)
|
104
|
+
|
83
105
|
ssh_rule = get_ssh_port_forwarding_rule(server, cached_rules)
|
84
|
-
if ssh_rule
|
85
|
-
|
86
|
-
|
106
|
+
return ssh_rule['ipaddress'] if ssh_rule
|
107
|
+
|
108
|
+
winrm_rule = get_winrm_port_forwarding_rule(server, cached_rules)
|
109
|
+
return winrm_rule['ipaddress'] if winrm_rule
|
110
|
+
|
87
111
|
#check for static NAT
|
88
|
-
|
112
|
+
if cached_nat
|
113
|
+
ip_addr = cached_nat.find {|v| v['virtualmachineid'] == server['id']}
|
114
|
+
else
|
115
|
+
ip_addr = list_public_ip_addresses.find {|v| v['virtualmachineid'] == server['id']}
|
116
|
+
end
|
89
117
|
if ip_addr
|
90
118
|
return ip_addr['ipaddress']
|
91
119
|
end
|
@@ -116,23 +144,15 @@ module CloudstackClient
|
|
116
144
|
end
|
117
145
|
end
|
118
146
|
|
119
|
-
##
|
147
|
+
##
|
120
148
|
# List all the objects based on the command that is specified.
|
121
|
-
|
122
|
-
def list_object(command, json_result, filter=nil, listall=nil, keyword=nil, name=nil,
|
123
|
-
params =
|
124
|
-
'command' => command
|
125
|
-
}
|
149
|
+
|
150
|
+
def list_object(command, json_result, filter=nil, listall=nil, keyword=nil, name=nil, params={})
|
151
|
+
params['command'] = command
|
126
152
|
params['listall'] = true if listall || name || keyword unless listall == false
|
127
153
|
params['keyword'] = keyword if keyword
|
128
154
|
params['name'] = name if name
|
129
155
|
|
130
|
-
if templatefilter
|
131
|
-
template = 'featured'
|
132
|
-
template = templatefilter.downcase if ["featured","self","self-executable","executable","community"].include?(templatefilter.downcase)
|
133
|
-
params['templateFilter'] = template
|
134
|
-
end
|
135
|
-
|
136
156
|
json = send_request(params)
|
137
157
|
Chef::Log.debug("JSON (list_object) result: #{json}")
|
138
158
|
|
@@ -155,7 +175,7 @@ module CloudstackClient
|
|
155
175
|
##
|
156
176
|
# Deploys a new server using the specified parameters.
|
157
177
|
|
158
|
-
def create_server(host_name, service_name, template_name, zone_name=nil, network_names=[], extra_params)
|
178
|
+
def create_server(host_name, service_name, template_name, disk_name=nil, zone_name=nil, network_names=[], extra_params)
|
159
179
|
|
160
180
|
if host_name then
|
161
181
|
if get_server(host_name) then
|
@@ -170,12 +190,20 @@ module CloudstackClient
|
|
170
190
|
exit 1
|
171
191
|
end
|
172
192
|
|
173
|
-
template = get_template(template_name)
|
193
|
+
template = get_template(template_name, zone_name)
|
174
194
|
if !template then
|
175
195
|
puts "Error: Template '#{template_name}' is invalid"
|
176
196
|
exit 1
|
177
197
|
end
|
178
198
|
|
199
|
+
if disk_name then
|
200
|
+
disk = get_disk_offering(disk_name)
|
201
|
+
if !disk then
|
202
|
+
puts "Error: Disk offering '#{disk_name}' is invalid"
|
203
|
+
exit 1
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
179
207
|
zone = zone_name ? get_zone(zone_name) : get_default_zone
|
180
208
|
if !zone then
|
181
209
|
msg = zone_name ? "Zone '#{zone_name}' is invalid" : "No default zone found"
|
@@ -183,37 +211,56 @@ module CloudstackClient
|
|
183
211
|
exit 1
|
184
212
|
end
|
185
213
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
214
|
+
if zone['networktype'] != 'Basic' then
|
215
|
+
# If this is a Basic zone no networkids are needed in the params
|
216
|
+
|
217
|
+
networks = []
|
218
|
+
if network_names.nil? then
|
219
|
+
networks << get_default_network(zone['id'])
|
220
|
+
else
|
221
|
+
network_names.each do |name|
|
222
|
+
network = get_network(name)
|
223
|
+
if !network then
|
224
|
+
puts "Error: Network '#{name}' not found"
|
225
|
+
end
|
226
|
+
networks << get_network(name)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
if networks.empty? then
|
231
|
+
networks << get_default_network(zone['id'])
|
232
|
+
end
|
233
|
+
if networks.empty? then
|
234
|
+
puts "No default network found"
|
191
235
|
exit 1
|
192
236
|
end
|
193
|
-
networks
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
237
|
+
network_ids = networks.map { |network|
|
238
|
+
network['id']
|
239
|
+
}
|
240
|
+
|
241
|
+
params = {
|
242
|
+
'command' => 'deployVirtualMachine',
|
243
|
+
'serviceOfferingId' => service['id'],
|
244
|
+
'templateId' => template['id'],
|
245
|
+
'zoneId' => zone['id'],
|
246
|
+
'networkids' => network_ids.join(',')
|
247
|
+
}
|
248
|
+
|
249
|
+
elsif
|
250
|
+
|
251
|
+
params = {
|
252
|
+
'command' => 'deployVirtualMachine',
|
253
|
+
'serviceOfferingId' => service['id'],
|
254
|
+
'templateId' => template['id'],
|
255
|
+
'zoneId' => zone['id']
|
256
|
+
}
|
205
257
|
|
206
|
-
|
207
|
-
'command' => 'deployVirtualMachine',
|
208
|
-
'serviceOfferingId' => service['id'],
|
209
|
-
'templateId' => template['id'],
|
210
|
-
'zoneId' => zone['id'],
|
211
|
-
'networkids' => network_ids.join(',')
|
212
|
-
}
|
258
|
+
end
|
213
259
|
|
214
260
|
params.merge!(extra_params) if extra_params
|
215
261
|
|
216
262
|
params['name'] = host_name if host_name
|
263
|
+
params['diskOfferingId'] = disk['id'] if disk
|
217
264
|
|
218
265
|
json = send_async_request(params)
|
219
266
|
json['virtualmachine']
|
@@ -318,11 +365,12 @@ module CloudstackClient
|
|
318
365
|
return nil unless services
|
319
366
|
|
320
367
|
services.each { |s|
|
321
|
-
if
|
322
|
-
return s
|
368
|
+
if name.is_uuid? then
|
369
|
+
return s if s['id'] == name
|
370
|
+
else
|
371
|
+
return s if s['name'] == name
|
323
372
|
end
|
324
373
|
}
|
325
|
-
|
326
374
|
nil
|
327
375
|
end
|
328
376
|
|
@@ -337,32 +385,63 @@ module CloudstackClient
|
|
337
385
|
json['serviceoffering'] || []
|
338
386
|
end
|
339
387
|
|
388
|
+
def list_security_groups
|
389
|
+
params = {
|
390
|
+
'command' => 'listSecurityGroups'
|
391
|
+
}
|
392
|
+
json = send_request(params)
|
393
|
+
json['securitygroups'] || []
|
394
|
+
end
|
395
|
+
|
396
|
+
|
340
397
|
##
|
341
398
|
# Finds the template with the specified name.
|
342
399
|
|
343
|
-
def get_template(name)
|
400
|
+
def get_template(name, zone_name=nil)
|
344
401
|
|
345
402
|
# TODO: use name parameter
|
346
403
|
# listTemplates in CloudStack 2.2 doesn't seem to work
|
347
404
|
# when the name parameter is specified. When this is fixed,
|
348
405
|
# the name parameter should be added to the request.
|
406
|
+
|
407
|
+
zone = zone_name ? get_zone(zone_name) : get_default_zone
|
408
|
+
|
349
409
|
params = {
|
350
410
|
'command' => 'listTemplates',
|
351
|
-
'templateFilter' => 'executable'
|
411
|
+
'templateFilter' => 'executable',
|
352
412
|
}
|
413
|
+
params['zoneid'] = zone['id'] if zone
|
414
|
+
|
353
415
|
json = send_request(params)
|
354
416
|
|
355
417
|
templates = json['template']
|
356
|
-
|
357
|
-
return nil
|
358
|
-
end
|
418
|
+
return nil unless templates
|
359
419
|
|
360
420
|
templates.each { |t|
|
361
|
-
if
|
362
|
-
return t
|
421
|
+
if name.is_uuid? then
|
422
|
+
return t if t['id'] == name
|
423
|
+
else
|
424
|
+
return t if t['name'] == name
|
363
425
|
end
|
364
426
|
}
|
427
|
+
nil
|
428
|
+
end
|
429
|
+
|
430
|
+
##
|
431
|
+
# Finds the disk offering with the specified name.
|
432
|
+
|
433
|
+
def get_disk_offering(name)
|
434
|
+
params = {
|
435
|
+
'command' => 'listDiskOfferings',
|
436
|
+
}
|
437
|
+
json = send_request(params)
|
438
|
+
disks = json['diskoffering']
|
439
|
+
|
440
|
+
return nil if !disks
|
365
441
|
|
442
|
+
disks.each { |d|
|
443
|
+
return d if d['name'] == name
|
444
|
+
}
|
366
445
|
nil
|
367
446
|
end
|
368
447
|
|
@@ -397,12 +476,14 @@ module CloudstackClient
|
|
397
476
|
json = send_request(params)
|
398
477
|
projects = json['project']
|
399
478
|
return nil unless projects
|
479
|
+
|
400
480
|
projects.each { |n|
|
401
|
-
if
|
402
|
-
return n
|
481
|
+
if name.is_uuid? then
|
482
|
+
return n if n['id'] == name
|
483
|
+
else
|
484
|
+
return n if n['name'] == name
|
403
485
|
end
|
404
486
|
}
|
405
|
-
|
406
487
|
nil
|
407
488
|
end
|
408
489
|
|
@@ -436,11 +517,12 @@ module CloudstackClient
|
|
436
517
|
return nil unless networks
|
437
518
|
|
438
519
|
networks.each { |n|
|
439
|
-
if
|
440
|
-
return n
|
520
|
+
if name.is_uuid? then
|
521
|
+
return n if n['id'] == name
|
522
|
+
else
|
523
|
+
return n if n['name'] == name
|
441
524
|
end
|
442
525
|
}
|
443
|
-
|
444
526
|
nil
|
445
527
|
end
|
446
528
|
|
@@ -496,11 +578,12 @@ module CloudstackClient
|
|
496
578
|
return nil unless networks
|
497
579
|
|
498
580
|
networks.each { |z|
|
499
|
-
if
|
500
|
-
return z
|
581
|
+
if name.is_uuid? then
|
582
|
+
return z if z['id'] == name
|
583
|
+
else
|
584
|
+
return z if z['name'] == name
|
501
585
|
end
|
502
586
|
}
|
503
|
-
|
504
587
|
nil
|
505
588
|
end
|
506
589
|
|
@@ -516,7 +599,8 @@ module CloudstackClient
|
|
516
599
|
|
517
600
|
zones = json['zone']
|
518
601
|
return nil unless zones
|
519
|
-
|
602
|
+
# zones.sort! # sort zones so we always return the same zone
|
603
|
+
# !this gives error in our production environment so need to retest this
|
520
604
|
zones.first
|
521
605
|
end
|
522
606
|
|
@@ -545,8 +629,9 @@ module CloudstackClient
|
|
545
629
|
json['publicipaddress'].first
|
546
630
|
end
|
547
631
|
|
548
|
-
def list_public_ip_addresses()
|
549
|
-
params = { 'command' => 'listPublicIpAddresses'}
|
632
|
+
def list_public_ip_addresses(listall=false)
|
633
|
+
params = { 'command' => 'listPublicIpAddresses' }
|
634
|
+
params['listall'] = listall
|
550
635
|
|
551
636
|
json = send_request(params)
|
552
637
|
return json['publicipaddress'] || []
|
@@ -560,13 +645,13 @@ module CloudstackClient
|
|
560
645
|
'zoneId' => zone_id
|
561
646
|
}
|
562
647
|
#Choose the first network from the list
|
563
|
-
if networks.
|
564
|
-
params['networkId'] = get_network(networks.first)['id']
|
565
|
-
else
|
648
|
+
if networks.nil? || networks.empty?
|
566
649
|
default_network = get_default_network(zone_id)
|
567
650
|
params['networkId'] = default_network['id']
|
651
|
+
else
|
652
|
+
params['networkId'] = get_network(networks.first)['id']
|
568
653
|
end
|
569
|
-
|
654
|
+
Chef::Log.debug("associate ip params: #{params}")
|
570
655
|
json = send_async_request(params)
|
571
656
|
json['ipaddress']
|
572
657
|
end
|
@@ -600,15 +685,26 @@ module CloudstackClient
|
|
600
685
|
send_async_request(params)
|
601
686
|
end
|
602
687
|
|
603
|
-
def create_firewall_rule(ipaddress_id, protocol,
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
688
|
+
def create_firewall_rule(ipaddress_id, protocol, param3, param4, cidr_list)
|
689
|
+
if protocol == "ICMP"
|
690
|
+
params = {
|
691
|
+
'command' => 'createFirewallRule',
|
692
|
+
'ipaddressId' => ipaddress_id,
|
693
|
+
'protocol' => protocol,
|
694
|
+
'icmptype' => param3,
|
695
|
+
'icmpcode' => param4,
|
696
|
+
'cidrlist' => cidr_list
|
697
|
+
}
|
698
|
+
else
|
699
|
+
params = {
|
700
|
+
'command' => 'createFirewallRule',
|
701
|
+
'ipaddressId' => ipaddress_id,
|
702
|
+
'protocol' => protocol,
|
703
|
+
'startport' => param3,
|
704
|
+
'endport' => param4,
|
705
|
+
'cidrlist' => cidr_list
|
706
|
+
}
|
707
|
+
end
|
612
708
|
send_async_request(params)
|
613
709
|
end
|
614
710
|
|
@@ -629,11 +725,10 @@ module CloudstackClient
|
|
629
725
|
##
|
630
726
|
# Lists all port forwarding rules.
|
631
727
|
|
632
|
-
def list_port_forwarding_rules(ip_address_id=nil)
|
633
|
-
params = {
|
634
|
-
'command' => 'listPortForwardingRules'
|
635
|
-
}
|
728
|
+
def list_port_forwarding_rules(ip_address_id=nil, listall=false)
|
729
|
+
params = { 'command' => 'listPortForwardingRules' }
|
636
730
|
params['ipAddressId'] = ip_address_id if ip_address_id
|
731
|
+
params['listall'] = listall
|
637
732
|
json = send_request(params)
|
638
733
|
json['portforwardingrule']
|
639
734
|
end
|
@@ -649,6 +744,20 @@ module CloudstackClient
|
|
649
744
|
r['publicport'] == '22'
|
650
745
|
}.first
|
651
746
|
end
|
747
|
+
|
748
|
+
##
|
749
|
+
# Gets the WINRM port forwarding rule for the specified server.
|
750
|
+
|
751
|
+
def get_winrm_port_forwarding_rule(server, cached_rules=nil)
|
752
|
+
rules = cached_rules || list_port_forwarding_rules || []
|
753
|
+
rules.find_all { |r|
|
754
|
+
r['virtualmachineid'] == server['id'] &&
|
755
|
+
(r['privateport'] == '5985' &&
|
756
|
+
r['publicport'] == '5985') ||
|
757
|
+
(r['privateport'] == '5986' &&
|
758
|
+
r['publicport'] == '5986')
|
759
|
+
}.first
|
760
|
+
end
|
652
761
|
|
653
762
|
##
|
654
763
|
# Creates a port forwarding rule.
|
@@ -666,6 +775,25 @@ module CloudstackClient
|
|
666
775
|
json['portforwardingrule']
|
667
776
|
end
|
668
777
|
|
778
|
+
def http_client_builder
|
779
|
+
http_proxy = proxy_uri
|
780
|
+
if http_proxy.nil?
|
781
|
+
Net::HTTP
|
782
|
+
else
|
783
|
+
Chef::Log.debug("Using #{http_proxy.host}:#{http_proxy.port} for proxy")
|
784
|
+
user = http_proxy.user if http_proxy.user
|
785
|
+
pass = http_proxy.password if http_proxy.password
|
786
|
+
Net::HTTP.Proxy(http_proxy.host, http_proxy.port, user, pass)
|
787
|
+
end
|
788
|
+
end
|
789
|
+
|
790
|
+
def proxy_uri
|
791
|
+
return nil if @api_proxy.nil?
|
792
|
+
result = URI.parse(@api_proxy)
|
793
|
+
return result unless result.host.nil? || result.host.empty?
|
794
|
+
nil
|
795
|
+
end
|
796
|
+
|
669
797
|
##
|
670
798
|
# Sends a synchronous request to the CloudStack API and returns the response as a Hash.
|
671
799
|
#
|
@@ -688,20 +816,34 @@ module CloudstackClient
|
|
688
816
|
signature = Base64.encode64(signature).chomp
|
689
817
|
signature = CGI.escape(signature)
|
690
818
|
|
819
|
+
if @api_url.nil? || @api_url.empty?
|
820
|
+
puts "Error: Please specify a valid API URL."
|
821
|
+
exit 1
|
822
|
+
end
|
823
|
+
|
691
824
|
url = "#{@api_url}?#{data}&signature=#{signature}"
|
692
825
|
Chef::Log.debug("URL: #{url}")
|
693
826
|
uri = URI.parse(url)
|
694
|
-
|
695
|
-
http
|
696
|
-
|
827
|
+
|
828
|
+
http = http_client_builder.new(uri.host, uri.port)
|
829
|
+
|
830
|
+
if uri.scheme == "https"
|
831
|
+
http.use_ssl = true
|
832
|
+
# Still need to do some testing on SSL, so will fix this later
|
833
|
+
if @no_ssl_verify
|
834
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
835
|
+
else
|
836
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
837
|
+
end
|
838
|
+
end
|
697
839
|
request = Net::HTTP::Get.new(uri.request_uri)
|
698
840
|
response = http.request(request)
|
699
841
|
|
700
842
|
if !response.is_a?(Net::HTTPOK) then
|
701
843
|
case response.code
|
702
844
|
when "432"
|
703
|
-
puts "\n"
|
704
|
-
puts "Error #{response.code}: Your account does not have the right to execute this command or the command does not exist."
|
845
|
+
puts "\n"
|
846
|
+
puts "Error #{response.code}: Your account does not have the right to execute this command is locked or the command does not exist."
|
705
847
|
else
|
706
848
|
puts "Error #{response.code}: #{response.message}"
|
707
849
|
puts JSON.pretty_generate(JSON.parse(response.body))
|
@@ -736,6 +878,7 @@ module CloudstackClient
|
|
736
878
|
print "."
|
737
879
|
|
738
880
|
if status == 1 then
|
881
|
+
print "\n"
|
739
882
|
return json['jobresult']
|
740
883
|
elsif status == 2 then
|
741
884
|
print "\n"
|