knife-cloudstack 0.0.14 → 0.0.15
Sign up to get free protection for your applications and to get access to all the features.
- 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"
|