knife-cloudstack-fog 0.3.4 → 0.4.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.
data/README.rdoc CHANGED
@@ -1,3 +1,6 @@
1
+ [![Gem Version](https://badge.fury.io/rb/knife-cloudstack-fog.png)](http://badge.fury.io/rb/knife-cloudstack-fog)
2
+ [![Build Status](https://www.travis-ci.org/fifthecho/knife-cloudstack-fog.png?branch=master)](https://www.travis-ci.org/fifthecho/knife-cloudstack-fog)
3
+
1
4
  = Knife Cloudstack
2
5
 
3
6
  = DESCRIPTION:
@@ -23,298 +23,450 @@ require 'chef/json_compat'
23
23
  require 'chef/knife/cloudstack_base'
24
24
 
25
25
  class Chef
26
- class Knife
27
- class CloudstackServerCreate < Knife
28
-
29
- include Knife::CloudstackBase
30
-
31
- banner "knife cloudstack server create -s SERVICEID -t TEMPLATEID -z ZONEID (options)"
32
-
33
- option :cloudstack_serviceid,
34
- :short => "-s SERVICEID",
35
- :long => "--serviceid SERVICEID",
36
- :description => "The CloudStack service offering ID."
37
-
38
- option :cloudstack_templateid,
39
- :short => "-t TEMPLATEID",
40
- :long => "--templateid TEMPLATEID",
41
- :description => "The CloudStack template ID for the server."
42
-
43
- option :cloudstack_zoneid,
44
- :short => "-z ZONEID",
45
- :long => "--zoneid ZONE",
46
- :description => "The CloudStack zone ID for the server."
47
-
48
- option :cloudstack_networkids,
49
- :short => "-w NETWORKIDS",
50
- :long => "--networkids NETWORKIDS",
51
- :description => "Comma separated list of CloudStack network IDs.",
52
- :proc => lambda { |n| n.split(/[\s,]+/) },
53
- :default => []
54
-
55
- option :cloudstack_groupids,
56
- :short => "-g SECURITYGROUPIDS",
57
- :long => "--groupids SECURITYGROUPIDS",
58
- :description => "Comma separated list of CloudStack Security Group IDs.",
59
- :proc => lambda { |n| n.split(/[\s,]+/) },
60
- :default => []
61
-
62
- option :cloudstack_groupnames,
63
- :short => "-G SECURITYGROUPNAMES",
64
- :long => "--groupnames SECURITYGROUPNAMES",
65
- :description => "Comma separated list of CloudStack Security Group names. Each group name must be encapuslated in quotes if it contains whitespace.",
66
- :proc => lambda { |n| n.split(/[\s,]+/) },
67
- :default => []
68
-
69
- option :distro,
70
- :short => "-d DISTRO",
71
- :long => "--distro DISTRO",
72
- :description => "Bootstrap a distro using a template; default is 'chef-full'",
73
- :proc => Proc.new { |d| Chef::Config[:knife][:distro] = d },
74
- :default => "chef-full"
75
-
76
- option :template_file,
77
- :long => "--template-file TEMPLATE",
78
- :description => "Full path to location of template to use",
79
- :proc => Proc.new { |t| Chef::Config[:knife][:template_file] = t },
80
- :default => false
81
-
82
- option :run_list,
83
- :short => "-r RUN_LIST",
84
- :long => "--run-list RUN_LIST",
85
- :description => "Comma separated list of roles/recipes to apply",
86
- :proc => lambda { |o| o.split(/[\s,]+/) },
87
- :default => []
88
-
89
- option :ssh_user,
90
- :short => "-x USERNAME",
91
- :long => "--ssh-user USERNAME",
92
- :description => "The ssh username",
93
- :default => 'root'
94
-
95
- option :ssh_password,
96
- :short => "-P PASSWORD",
97
- :long => "--ssh-password PASSWORD",
98
- :description => "The ssh password"
99
-
100
- option :identity_file,
101
- :short => "-i PRIVATE_KEY_FILE",
102
- :long => "--identity-file PRIVATE_KEY_FILE",
103
- :description => "The Private key file for authenticating SSH session. --keypair option is also needed."
104
-
105
- option :server_name,
106
- :short => "-N NAME",
107
- :long => "--display-name NAME",
108
- :description => "The instance display name"
109
-
110
- option :host_name,
111
- :short => "-H NAME",
112
- :long => "--hostname NAME",
113
- :description => "The instance host name"
114
-
115
- option :keypair,
116
- :short => "-k KEYPAIR",
117
- :long => "--keypair KEYPAIR",
118
- :description => "The CloudStack Key Pair to use for SSH key authentication."
119
-
120
- option :diskoffering,
121
- :short => "-D DISKOFFERINGID",
122
- :long => "--diskoffering DISKOFFERINGID",
123
- :description => "Specifies either the Disk Offering ID for the ROOT disk for an ISO template, or a DATA disk."
124
-
125
- option :size,
126
- :short => "-Z SIZE",
127
- :long => "--size SIZE",
128
- :description => "Specifies the arbitrary Disk Size for DATADISK volume in GB. Must be passed with custom size Disk Offering ID."
129
-
130
- def bootstrap_for_node(host, user, password)
131
- Chef::Log.debug("Bootstrap host: #{host}")
132
- Chef::Log.debug("Bootstrap user: #{user}")
133
- Chef::Log.debug("Bootstrap pass: #{password}")
134
- bootstrap = Chef::Knife::Bootstrap.new
135
- bootstrap.name_args = host
136
- bootstrap.config[:run_list] = config[:run_list]
137
- bootstrap.config[:ssh_user] = user
138
- bootstrap.config[:ssh_password] = password
139
- bootstrap.config[:identity_file] = locate_config_value(:identity_file)
140
- bootstrap.config[:chef_node_name] = config[:server_name] if config[:server_name]
141
- bootstrap.config[:prerelease] = config[:prerelease]
142
- bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
143
- bootstrap.config[:distro] = locate_config_value(:distro)
144
- bootstrap.config[:use_sudo] = true
145
- bootstrap.config[:template_file] = locate_config_value(:template_file)
146
- bootstrap.config[:environment] = config[:environment]
147
- # may be needed for vpc_mode
148
- bootstrap.config[:no_host_key_verify] = config[:no_host_key_verify]
149
- bootstrap
150
- end
151
-
152
- def tcp_test_ssh(hostname)
153
- print("#{ui.color(".", :magenta)}")
154
- tcp_socket = TCPSocket.new(hostname, 22)
155
- readable = IO.select([tcp_socket], nil, nil, 5)
156
- if readable
157
- Chef::Log.debug("\nsshd accepting connections on #{hostname}, banner is #{tcp_socket.gets}\n")
158
- yield
159
- true
160
- else
161
- false
162
- end
163
-
164
- rescue Errno::ETIMEDOUT
165
- false
166
- rescue Errno::EPERM
167
- false
168
- rescue Errno::ECONNREFUSED
169
- sleep 2
170
- false
171
- rescue Errno::EHOSTUNREACH
172
- sleep 2
173
- false
174
- rescue Errno::ENETUNREACH
175
- sleep 30
176
- false
177
- ensure
178
- tcp_socket && tcp_socket.close
179
- end
180
-
181
- def run
182
- $stdout.sync = true
183
-
184
- options = {}
185
-
186
- options['zoneid'] = locate_config_value(:cloudstack_zoneid)
187
- options['templateid'] = locate_config_value(:cloudstack_templateid)
188
-
189
- if locate_config_value(:cloudstack_serviceid) != nil
190
- options['serviceofferingid'] = locate_config_value(:cloudstack_serviceid)
191
- end
192
-
193
- if locate_config_value(:server_name) != nil
194
- options['displayname'] = locate_config_value(:server_name)
195
- end
196
-
197
- if locate_config_value(:host_name) != nil
198
- options['name'] = locate_config_value(:host_name)
199
- end
200
-
201
- network_ids = []
202
- if locate_config_value(:cloudstack_networkids) != []
203
- cs_networkids = locate_config_value(:cloudstack_networkids)
204
- cs_networkids.each do |id|
205
- network_ids.push(id)
206
- end
207
- options['networkids'] = network_ids
208
- end
209
-
210
- security_groups = []
211
- if locate_config_value(:cloudstack_groupids) != []
212
- cs_groupids = locate_config_value(:cloudstack_groupids)
213
- cs_groupids.each do |id|
214
- security_groups.push(id)
215
- end
216
- options['securitygroupids'] = security_groups
217
- elsif locate_config_value(:cloudstack_groupnames) != []
218
- cs_groupnames = locate_config_value(:cloudstack_groupnames)
219
- cs_groupnames.each do |name|
220
- security_groups.push(name)
221
- end
222
- options['securitygroupnames'] = security_groups
223
- end
224
-
225
- if locate_config_value(:keypair) != nil
226
- options['keypair'] = locate_config_value(:keypair)
227
- end
228
-
229
- if locate_config_value(:diskoffering) != nil
230
- options['diskofferingid'] = locate_config_value(:diskoffering)
231
- end
232
-
233
- if locate_config_value(:size) != nil
234
- options['size'] = locate_config_value(:size)
235
- end
236
-
237
- Chef::Log.debug("Options: #{options} \n")
238
-
239
- server = connection.deploy_virtual_machine(options)
240
- jobid = server['deployvirtualmachineresponse'].fetch('jobid')
241
-
242
- server_start = connection.query_async_job_result('jobid'=>jobid)
243
-
244
- Chef::Log.debug("Job ID: #{jobid} \n")
245
-
246
- print "#{ui.color("Waiting for server", :magenta)}"
247
- while server_start['queryasyncjobresultresponse'].fetch('jobstatus') == 0
248
- print "#{ui.color(".", :magenta)}"
249
- sleep(15)
250
- server_start = connection.query_async_job_result('jobid'=>jobid)
251
- Chef::Log.debug("Server_Start: #{server_start} \n")
252
- end
253
- puts "\n\n"
254
-
255
- if server_start['queryasyncjobresultresponse'].fetch('jobstatus') == 2
256
- errortext = server_start['queryasyncjobresultresponse'].fetch('jobresult').fetch('errortext')
257
- puts "#{ui.color("ERROR! Job failed with #{errortext}", :red)}"
258
- end
259
-
260
- if server_start['queryasyncjobresultresponse'].fetch('jobstatus') == 1
261
-
262
- Chef::Log.debug("Job ID: #{jobid} \n")
263
- Chef::Log.debug("Options: #{options} \n")
264
- server_start = connection.query_async_job_result('jobid'=>jobid)
265
- Chef::Log.debug("Server_Start: #{server_start} \n")
266
-
267
- server_info = server_start['queryasyncjobresultresponse']['jobresult']['virtualmachine']
268
-
269
- server_name = server_info['displayname']
270
- server_id = server_info['name']
271
- server_serviceoffering = server_info['serviceofferingname']
272
- server_template = server_info['templatename']
273
- if server_info['password'] != nil
274
- ssh_password = server_info['password']
275
- else
276
- ssh_password = locate_config_value(:ssh_password)
277
- end
278
-
279
- ssh_user = locate_config_value(:ssh_user)
280
-
281
- public_ip = nil
282
-
283
- if server_info['nic'].size > 0
284
- public_ip = server_info['nic'].first['ipaddress']
285
- end
286
-
287
- puts "\n\n"
288
- puts "#{ui.color("Name", :cyan)}: #{server_name}"
289
- puts "#{ui.color("Public IP", :cyan)}: #{public_ip}"
290
- puts "#{ui.color("Username", :cyan)}: #{ssh_user}"
291
- puts "#{ui.color("Password", :cyan)}: #{ssh_password}"
292
-
293
- print "\n#{ui.color("Waiting for sshd", :magenta)}"
294
-
295
- print("#{ui.color(".", :magenta)}") until tcp_test_ssh(public_ip) { sleep @initial_sleep_delay ||= 10; puts("done") }
296
-
297
- puts("#{ui.color("Waiting for password/keys to sync.", :magenta)}")
298
- sleep 15
299
-
300
- bootstrap_for_node(public_ip, ssh_user, ssh_password).run
301
-
302
- Chef::Log.debug("#{server_info}")
303
-
304
- puts "\n"
305
- puts "#{ui.color("Instance Name", :green)}: #{server_name}"
306
- puts "#{ui.color("Instance ID", :green)}: #{server_id}"
307
- puts "#{ui.color("Service Offering", :green)}: #{server_serviceoffering}"
308
- puts "#{ui.color("Template", :green)}: #{server_template}"
309
- puts "#{ui.color("Public IP Address", :green)}: #{public_ip}"
310
- puts "#{ui.color("User", :green)}: #{ssh_user}"
311
- puts "#{ui.color("Password", :green)}: #{ssh_password}"
312
- puts "#{ui.color("Environment", :green)}: #{config[:environment] || '_default'}"
313
- puts "#{ui.color("Run List", :green)}: #{config[:run_list].join(', ')}"
314
- end
315
-
316
- end
26
+ class Knife
27
+ class CloudstackServerCreate < Knife
28
+
29
+ include Knife::CloudstackBase
30
+
31
+ banner "knife cloudstack server create -s SERVICEID -t TEMPLATEID -z ZONEID (options)"
32
+
33
+ option :cloudstack_serviceid,
34
+ :short => "-s SERVICEID",
35
+ :long => "--serviceid SERVICEID",
36
+ :description => "The CloudStack service offering ID."
37
+
38
+ option :cloudstack_templateid,
39
+ :short => "-t TEMPLATEID",
40
+ :long => "--templateid TEMPLATEID",
41
+ :description => "The CloudStack template ID for the server."
42
+
43
+ option :cloudstack_zoneid,
44
+ :short => "-z ZONEID",
45
+ :long => "--zoneid ZONE",
46
+ :description => "The CloudStack zone ID for the server."
47
+
48
+ option :cloudstack_networkids,
49
+ :short => "-w NETWORKIDS",
50
+ :long => "--networkids NETWORKIDS",
51
+ :description => "Comma separated list of CloudStack network IDs.",
52
+ :proc => lambda { |n| n.split(/[\s,]+/) },
53
+ :default => []
54
+
55
+ option :cloudstack_groupids,
56
+ :short => "-g SECURITYGROUPIDS",
57
+ :long => "--groupids SECURITYGROUPIDS",
58
+ :description => "Comma separated list of CloudStack Security Group IDs.",
59
+ :proc => lambda { |n| n.split(/[\s,]+/) },
60
+ :default => []
61
+
62
+ option :cloudstack_groupnames,
63
+ :short => "-G SECURITYGROUPNAMES",
64
+ :long => "--groupnames SECURITYGROUPNAMES",
65
+ :description => "Comma separated list of CloudStack Security Group names. Each group name must be encapuslated in quotes if it contains whitespace.",
66
+ :proc => lambda { |n| n.split(/[\s,]+/) },
67
+ :default => []
68
+
69
+ option :distro,
70
+ :short => "-d DISTRO",
71
+ :long => "--distro DISTRO",
72
+ :description => "Bootstrap a distro using a template; default is 'chef-full'",
73
+ :proc => Proc.new { |d| Chef::Config[:knife][:distro] = d },
74
+ :default => "chef-full"
75
+
76
+ option :template_file,
77
+ :long => "--template-file TEMPLATE",
78
+ :description => "Full path to location of template to use",
79
+ :proc => Proc.new { |t| Chef::Config[:knife][:template_file] = t },
80
+ :default => false
81
+
82
+ option :run_list,
83
+ :short => "-r RUN_LIST",
84
+ :long => "--run-list RUN_LIST",
85
+ :description => "Comma separated list of roles/recipes to apply",
86
+ :proc => lambda { |o| o.split(/[\s,]+/) },
87
+ :default => []
88
+
89
+ option :ssh_user,
90
+ :short => "-x USERNAME",
91
+ :long => "--ssh-user USERNAME",
92
+ :description => "The ssh username",
93
+ :default => 'root'
94
+
95
+ option :ssh_password,
96
+ :short => "-P PASSWORD",
97
+ :long => "--ssh-password PASSWORD",
98
+ :description => "The ssh password"
99
+
100
+ option :identity_file,
101
+ :short => "-i PRIVATE_KEY_FILE",
102
+ :long => "--identity-file PRIVATE_KEY_FILE",
103
+ :description => "The Private key file for authenticating SSH session. --keypair option is also needed."
104
+
105
+ option :ssh_port,
106
+ :short => "-p PORT",
107
+ :long => "--ssh-port PORT",
108
+ :description => "The port which SSH should be listening on. If unspecified, will default to 22."
109
+
110
+ option :server_name,
111
+ :short => "-N NAME",
112
+ :long => "--display-name NAME",
113
+ :description => "The instance display name"
114
+
115
+ option :host_name,
116
+ :short => "-H NAME",
117
+ :long => "--hostname NAME",
118
+ :description => "The instance host name"
119
+
120
+ option :keypair,
121
+ :short => "-k KEYPAIR",
122
+ :long => "--keypair KEYPAIR",
123
+ :description => "The CloudStack Key Pair to use for SSH key authentication."
124
+
125
+ option :diskoffering,
126
+ :short => "-D DISKOFFERINGID",
127
+ :long => "--diskoffering DISKOFFERINGID",
128
+ :description => "Specifies either the Disk Offering ID for the ROOT disk for an ISO template, or a DATA disk."
129
+
130
+ option :size,
131
+ :short => "-Z SIZE",
132
+ :long => "--size SIZE",
133
+ :description => "Specifies the arbitrary Disk Size for DATADISK volume in GB. Must be passed with custom size Disk Offering ID."
134
+
135
+ option :random_ssh_port,
136
+ :long => "--random-ssh-port",
137
+ :description => "Map a random, unused high-level port to 22 for SSH and creates a port forward for this mapping. For Isolated Networking and VPC only."
138
+
139
+ option :ssh_gateway,
140
+ :short => "-W GATEWAY",
141
+ :long => "--ssh-gateway GATEWAY",
142
+ :description => "The ssh gateway server. Connection is defined as USERNAME@HOST:PORT",
143
+ :proc => Proc.new { |key| Chef::Config[:knife][:ssh_gateway] = key }
144
+
145
+ # def bootstrap_for_node(host, user, password)
146
+ def bootstrap_for_node(server, ssh_host)
147
+ host = server["name"]
148
+ user = config[:ssh_user]
149
+ password = server["password"]
150
+ Chef::Log.debug("Bootstrap host: #{host}")
151
+ Chef::Log.debug("Bootstrap user: #{user}")
152
+ Chef::Log.debug("Bootstrap pass: #{password}")
153
+ bootstrap = Chef::Knife::Bootstrap.new
154
+ bootstrap.name_args = [ssh_host]
155
+ bootstrap.config[:run_list] = config[:run_list]
156
+ bootstrap.config[:ssh_user] = user
157
+ bootstrap.config[:ssh_password] = password
158
+ bootstrap.config[:ssh_gateway] = config[:ssh_gateway]
159
+ bootstrap.config[:identity_file] = locate_config_value(:identity_file)
160
+ bootstrap.config[:chef_node_name] = config[:server_name] if config[:server_name]
161
+ bootstrap.config[:prerelease] = config[:prerelease]
162
+ bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
163
+ bootstrap.config[:distro] = locate_config_value(:distro)
164
+ bootstrap.config[:use_sudo] = true
165
+ bootstrap.config[:template_file] = locate_config_value(:template_file)
166
+ bootstrap.config[:environment] = config[:environment]
167
+ # may be needed for vpc_mode
168
+ bootstrap.config[:no_host_key_verify] = config[:no_host_key_verify]
169
+ begin
170
+ bootstrap
171
+ rescue
172
+ sleep @initial_sleep_delay
173
+ retry
174
+ end
175
+ end
176
+
177
+ def vpc_mode?
178
+ # Virtual Private Cloud / Isolated Networking requires a network id. If
179
+ # present, do a few things differently
180
+ !!locate_config_value(:cloudstack_networkids)
181
+ end
182
+
183
+ def wait_for_sshd(hostname)
184
+ config[:ssh_gateway] ? wait_for_tunnelled_sshd(hostname) : wait_for_direct_sshd(hostname, @sshport)
185
+ end
186
+
187
+ def wait_for_tunnelled_sshd(hostname)
188
+ Chef::Log.debug("Connecting to #{hostname} via wait_for_tunnelled_sshd")
189
+ print("#{ui.color(".", :magenta)}")
190
+ print("#{ui.color(".", :magenta)}") until tunnel_test_ssh(ssh_connect_host) {
191
+ sleep @initial_sleep_delay ||= (vpc_mode? ? 40 : 10)
192
+ puts("#{ui.color(". Done.", :magenta)}")
193
+ }
194
+ end
195
+
196
+ def tunnel_test_ssh(hostname, &block)
197
+ gw_host, gw_user = config[:ssh_gateway].split('@').reverse
198
+ gw_host, gw_port = gw_host.split(':')
199
+ Chef::Log.debug("Connecting to #{hostname} via #{gw_host} over port #{gw_port}.")
200
+ gateway = Net::SSH::Gateway.new(gw_host, gw_user, :port => gw_port || 22)
201
+ status = false
202
+ gateway.open(hostname, config[:ssh_port]) do |local_tunnel_port|
203
+ status = tcp_test_ssh('localhost', local_tunnel_port, &block)
204
+ Chef::Log.debug "Opened local port #{local_tunnel_port} to tunnel the connection."
205
+ end
206
+ status
207
+ rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, IOError
208
+ sleep 2
209
+ false
210
+ rescue Errno::EPERM, Errno::ETIMEDOUT
211
+ false
212
+ rescue Errno::Disconnect
213
+ sleep @initial_sleep_delay
214
+ retry
215
+ end
216
+
217
+ def wait_for_direct_sshd(hostname, ssh_port)
218
+ Chef::Log.debug("Connecting directly to #{hostname} over port #{ssh_port}")
219
+ print("#{ui.color(".", :magenta)}") until tcp_test_ssh(ssh_connect_host, ssh_port) {
220
+ sleep @initial_sleep_delay ||= (vpc_mode? ? 40 : 10)
221
+ puts("#{ui.color(". Done.", :magenta)}")
222
+ }
223
+ end
224
+
225
+ def ssh_connect_host
226
+ @ssh_connect_host ||= if config[:server_connect_attribute]
227
+ server.send(config[:server_connect_attribute])
228
+ else
229
+ Chef::Log.debug("Connecting to #{@primary_ip}")
230
+ @primary_ip
231
+ # vpc_mode? ? server.private_ip_address : server.dns_name
232
+ end
233
+ end
234
+
235
+ def tcp_test_ssh(hostname, ssh_port)
236
+ Chef::Log.debug("Conecting to #{hostname} on #{ssh_port}.")
237
+ print("#{ui.color(".", :magenta)}")
238
+ tcp_socket = TCPSocket.new(hostname, ssh_port)
239
+ readable = IO.select([tcp_socket], nil, nil, 5)
240
+ if readable
241
+ Chef::Log.debug("sshd accepting connections on #{hostname}, banner is #{tcp_socket.gets}")
242
+ yield
243
+ true
244
+ else
245
+ false
246
+ end
247
+ rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, IOError
248
+ sleep 2
249
+ false
250
+ rescue Errno::EPERM, Errno::ETIMEDOUT
251
+ false
252
+ rescue Errno::Disconnect
253
+ sleep @initial_sleep_delay
254
+ retry
255
+ ensure
256
+ tcp_socket && tcp_socket.close
257
+ end
258
+
259
+ def check_port_available(public_port, ipaddressid)
260
+ Chef::Log.debug("Checking if port #{public_port} is available.")
261
+ pubport = public_port.to_i
262
+ port_forward_rules_query = connection.list_port_forwarding_rules({'ipaddressid' => ipaddressid })
263
+ port_rules = port_forward_rules_query['listportforwardingrulesresponse']['portforwardingrule']
264
+ is_available = true
265
+ some_possible_rules = port_rules.select { |rule| rule['publicport'].to_i <= pubport }
266
+ possible_rules = some_possible_rules.select { |rule| rule['publicendport'].to_i >= pubport }
267
+ possible_rules.each do |rule|
268
+ startport = rule['publicport'].to_i
269
+ endport = rule['publicendport'].to_i
270
+ Chef::Log.debug("Determining if #{pubport} is between #{startport} and #{endport}.")
271
+ if (endport != startport)
272
+ if pubport.between?(startport, endport)
273
+ is_available = false
274
+ else
275
+ is_available = true
276
+ end
277
+ else
278
+ if (pubport == startport)
279
+ is_available = false
280
+ else
281
+ is_available = true
282
+ end
283
+ end
284
+ end
285
+ return is_available
286
+ end
287
+
288
+ def add_port_forward(public_start_port, public_end_port, server_id, ipaddressid, privateport)
289
+ pfwdops = {}
290
+ pfwdops['ipaddressid'] = ipaddressid
291
+ pfwdops['privateport'] = privateport
292
+ pfwdops['protocol'] = "TCP"
293
+ pfwdops['virtualmachineid'] = server_id
294
+ pfwdops['openfirewall'] = "true"
295
+ pfwdops['publicport'] = public_start_port
296
+ pfwdops['publicendport'] = public_end_port
297
+ rule_create_job = connection.create_port_forwarding_rule(pfwdops)
298
+ print "#{ui.color("Creating port forwarding rule.", :cyan)}"
299
+ while (@connection.query_async_job_result({'jobid' => rule_create_job['createportforwardingruleresponse']['jobid']})['queryasyncjobresultresponse'].fetch('jobstatus') == 0)
300
+ print("#{ui.color(".", :cyan)}")
301
+ sleep 2
302
+ end
303
+ print("\n")
304
+ end
305
+
306
+ def create_server_def
307
+ server_def = {
308
+ "templateid" => locate_config_value(:cloudstack_templateid),
309
+ "serviceofferingid" => locate_config_value(:cloudstack_serviceid),
310
+ "zoneid" => locate_config_value(:cloudstack_zoneid)
311
+ }
312
+
313
+ if locate_config_value(:server_name) != nil
314
+ server_def["displayname"] = locate_config_value(:server_name)
315
+ end
316
+
317
+ if locate_config_value(:host_name) != nil
318
+ server_def["name"] = locate_config_value(:host_name)
319
+ end
320
+
321
+ network_ids = []
322
+ if locate_config_value(:cloudstack_networkids) != []
323
+ cs_networkids = locate_config_value(:cloudstack_networkids)
324
+ cs_networkids.each do |id|
325
+ network_ids.push(id)
326
+ end
327
+ server_def["networkids"] = network_ids
328
+ end
329
+
330
+ security_groups = []
331
+ if locate_config_value(:cloudstack_groupids) != []
332
+ cs_groupids = locate_config_value(:cloudstack_groupids)
333
+ cs_groupids.each do |id|
334
+ security_groups.push(id)
335
+ end
336
+ server_def["securitygroupids"] = security_groups
337
+ elsif locate_config_value(:cloudstack_groupnames) != []
338
+ cs_groupnames = locate_config_value(:cloudstack_groupnames)
339
+ cs_groupnames.each do |name|
340
+ security_groups.push(name)
341
+ end
342
+ server_def["securitygroupnames"] = security_groups
343
+ end
344
+
345
+ if locate_config_value(:keypair) != nil
346
+ server_def["keypair"] = locate_config_value(:keypair)
347
+ end
348
+
349
+ if locate_config_value(:diskoffering) != nil
350
+ server_def["diskofferingid"] = locate_config_value(:diskoffering)
351
+ end
352
+
353
+ if locate_config_value(:size) != nil
354
+ server_def["size"] = locate_config_value(:size)
355
+ end
356
+
357
+ server_def
358
+ end
359
+
360
+ def run
361
+ $stdout.sync = true
362
+ options = create_server_def
363
+ Chef::Log.debug("Options: #{options} \n")
364
+
365
+ @initial_sleep_delay = 10
366
+ @sshport = 22
367
+ if locate_config_value(:ssh_port) != nil
368
+ @sshport = locate_config_value(:ssh_port).to_i
369
+ end
370
+
371
+ serverdeploy = connection.deploy_virtual_machine(options)
372
+ jobid = serverdeploy['deployvirtualmachineresponse'].fetch('jobid')
373
+
374
+ server_start = connection.query_async_job_result('jobid'=>jobid)
375
+
376
+ Chef::Log.debug("Job ID: #{jobid} \n")
377
+
378
+ print "#{ui.color("Waiting for server", :magenta)}"
379
+ while server_start['queryasyncjobresultresponse'].fetch('jobstatus') == 0
380
+ print "#{ui.color(".", :magenta)}"
381
+ sleep @initial_sleep_delay
382
+ server_start = connection.query_async_job_result('jobid'=>jobid)
383
+ Chef::Log.debug("Server_Start: #{server_start} \n")
384
+ end
385
+ puts "\n\n"
386
+
387
+ if server_start['queryasyncjobresultresponse'].fetch('jobstatus') == 2
388
+ errortext = server_start['queryasyncjobresultresponse'].fetch('jobresult').fetch('errortext')
389
+ puts "#{ui.color("ERROR! Job failed with #{errortext}", :red)}"
390
+ end
391
+
392
+ if server_start['queryasyncjobresultresponse'].fetch('jobstatus') == 1
393
+
394
+ Chef::Log.debug("Job ID: #{jobid} \n")
395
+ Chef::Log.debug("Options: #{options} \n")
396
+ server_start = connection.query_async_job_result('jobid'=>jobid)
397
+ Chef::Log.debug("Server_Start: #{server_start} \n")
398
+
399
+ @server = server_start['queryasyncjobresultresponse']['jobresult']['virtualmachine']
400
+
401
+ server_name = @server['displayname']
402
+ server_id = @server['name']
403
+ server_serviceoffering = @server['serviceofferingname']
404
+ server_template = @server['templatename']
405
+ if @server['password'] != nil
406
+ ssh_password = @server['password']
407
+ else
408
+ ssh_password = locate_config_value(:ssh_password)
409
+ end
410
+
411
+ ssh_user = locate_config_value(:ssh_user)
412
+
413
+ @primary_ip = nil
414
+
415
+ if @server['nic'].size > 0
416
+ @primary_ip = @server['nic'].first['ipaddress']
417
+ end
418
+
419
+ if locate_config_value(:random_ssh_port) != nil
420
+ public_ips = connection.list_public_ip_addresses("associatednetworkid" => @server['nic'][0]['networkid'])
421
+ primary_public_ip_id = public_ips['listpublicipaddressesresponse']['publicipaddress'][0]['id']
422
+ @primary_ip = public_ips['listpublicipaddressesresponse']['publicipaddress'][0]['ipaddress']
423
+ pubport = rand(49152..65535)
424
+ while (check_port_available(pubport, primary_public_ip_id) == false)
425
+ pubport = rand(49152..65535)
426
+ end
427
+ add_port_forward(pubport, pubport, server_id, primary_public_ip_id, @sshport)
428
+ @sshport = pubport
429
+ end
430
+
431
+
432
+
433
+ Chef::Log.debug("Connecting over port #{@sshport}")
434
+
435
+ puts "\n\n"
436
+ puts "#{ui.color("Name", :cyan)}: #{server_name}"
437
+ puts "#{ui.color("Primary IP", :cyan)}: #{@primary_ip}"
438
+ puts "#{ui.color("Username", :cyan)}: #{ssh_user}"
439
+ puts "#{ui.color("Password", :cyan)}: #{ssh_password}"
440
+
441
+ print "\n#{ui.color("Waiting for sshd", :magenta)}"
442
+ wait_for_sshd(ssh_connect_host)
443
+
444
+ puts("#{ui.color("Waiting for password/keys to sync.", :magenta)}")
445
+ sleep @initial_sleep_delay
446
+ sleep @initial_sleep_delay
447
+ sleep @initial_sleep_delay
448
+
449
+ Chef::Log.debug("Connnecting to #{@server} via #{ssh_connect_host} and bootstrapping Chef.")
450
+
451
+ bootstrap_for_node(@server,ssh_connect_host).run
452
+
453
+ Chef::Log.debug("#{@server}")
454
+
455
+ puts "\n"
456
+ puts "#{ui.color("Instance Name", :green)}: #{server_name}"
457
+ puts "#{ui.color("Instance ID", :green)}: #{server_id}"
458
+ puts "#{ui.color("Service Offering", :green)}: #{server_serviceoffering}"
459
+ puts "#{ui.color("Template", :green)}: #{server_template}"
460
+ puts "#{ui.color("Public IP Address", :green)}: #{@primary_ip}"
461
+ puts "#{ui.color("Port", :green)}: #{@sshport}"
462
+ puts "#{ui.color("User", :green)}: #{ssh_user}"
463
+ puts "#{ui.color("Password", :green)}: #{ssh_password}"
464
+ puts "#{ui.color("Environment", :green)}: #{config[:environment] || '_default'}"
465
+ puts "#{ui.color("Run List", :green)}: #{config[:run_list].join(', ')}"
466
+ end
467
+
468
+ end
317
469
 
318
- end
319
- end
470
+ end
471
+ end
320
472
  end
@@ -1,6 +1,6 @@
1
1
  module Knife
2
2
  module Cloudstack
3
- VERSION = "0.3.4"
3
+ VERSION = "0.4.0"
4
4
  MAJOR, MINOR, TINY = VERSION.split('.')
5
5
  end
6
- end
6
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: knife-cloudstack-fog
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.4
4
+ version: 0.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -13,7 +13,7 @@ authors:
13
13
  autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
- date: 2013-07-18 00:00:00.000000000 Z
16
+ date: 2013-10-14 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: fog
@@ -104,7 +104,8 @@ files:
104
104
  - README.rdoc
105
105
  - LICENSE
106
106
  homepage: https://github.com/fifthecho/knife-cloudstack-fog
107
- licenses: []
107
+ licenses:
108
+ - Apache 2.0
108
109
  post_install_message:
109
110
  rdoc_options: []
110
111
  require_paths: