knife-cloudstack-fog 0.3.4 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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: