knife-azure 1.1.2 → 1.1.4.rc.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -119,6 +119,7 @@ https://github.com/opscode/knife-windows#nodes
119
119
  :azure_connect_to_existing_dns Set this flag to add the new VM to an existing
120
120
  deployment/service. Must give the name of the existing
121
121
  DNS correctly in the --azure-dns-name option
122
+ :azure_availability_set Optional. Name of availability set to add virtual machine into.
122
123
 
123
124
  #### Azure VM Quick Create
124
125
  You can create a server with minimal configuration. On the Azure Management Portal, this corresponds to a "Quick Create - VM". Sample command for quick create (for an Ubuntu instance):
@@ -219,11 +220,12 @@ Sample knife.rb for bootstrapping Windows Node with basic authentication
219
220
  ### Azure Server Delete Subcommand
220
221
  Deletes an existing server in the currently configured Azure account. By
221
222
  default, this does not delete the associated node and client objects from the
222
- Chef server. To do so, add the --purge flag. Also by default, the DNS name, also called "cloud service", is deleted if you are deleting the last VM from that service. By default, the OS disk is also deleted. If you want to retain them add the --preserve flag as shown below. To delete the storage account, add the --delete-azure-storage-account flag since by default the storage account is not deleted.
223
+ Chef server. To do so, add the --purge flag. Also by default, the DNS name, also called "cloud service", is deleted if you are deleting the last VM from that service. By default, the OS disk is also deleted. The underlying VHD blob is also deleted by default. If you want to retain them add the --preserve flag as shown below. To delete the storage account, add the --delete-azure-storage-account flag since by default the storage account is not deleted.
223
224
 
224
225
  knife azure server delete "myvm01"
225
226
  knife azure server delete "myvm01" --purge #purge chef node
226
227
  knife azure server delete "myvm01" --preserve-azure-os-disk
228
+ knife azure server delete "myvm01" --preserve-azure-vhd
227
229
  knife azure server delete "myvm01" --preserve-azure-dns-name
228
230
  knife azure server delete "myvm01" --delete-azure-storage-account
229
231
 
@@ -39,10 +39,10 @@ class Azure
39
39
  @disks = Disks.new(self)
40
40
  @certificates = Certificates.new(self)
41
41
  end
42
- def query_azure(service_name, verb = 'get', body = '')
42
+ def query_azure(service_name, verb = 'get', body = '', params = '')
43
43
  Chef::Log.info 'calling ' + verb + ' ' + service_name
44
44
  Chef::Log.debug body unless body == ''
45
- response = @rest.query_azure(service_name, verb, body)
45
+ response = @rest.query_azure(service_name, verb, body, params)
46
46
  if response.code.to_i == 200
47
47
  ret_val = Nokogiri::XML response.body
48
48
  elsif response.code.to_i >= 201 && response.code.to_i <= 299
@@ -89,13 +89,18 @@ class Azure
89
89
  ret_val = deploy.create(params, deployXML)
90
90
  end
91
91
  if ret_val.css('Error Code').length > 0
92
- Chef::Log.fatal 'Unable to create role:' + ret_val.at_css('Error Code').content + ' : ' + ret_val.at_css('Error Message').content
93
- exit 1
92
+ raise Chef::Log.fatal 'Unable to create role:' + ret_val.at_css('Error Code').content + ' : ' + ret_val.at_css('Error Message').content
94
93
  end
95
94
  @connection.roles.find_in_hosted_service(params[:azure_vm_name], params[:azure_dns_name])
96
95
  end
97
96
  def delete(rolename)
98
97
  end
98
+
99
+ def queryDeploy(hostedservicename)
100
+ deploy = Deploy.new(@connection)
101
+ deploy.retrieve(hostedservicename)
102
+ deploy
103
+ end
99
104
  end
100
105
 
101
106
  class Deploy
@@ -134,6 +139,9 @@ class Azure
134
139
  xml.DeploymentSlot 'Production'
135
140
  xml.Label Base64.encode64(params['deploy_name']).strip
136
141
  xml.RoleList { xml.Role('i:type'=>'PersistentVMRole') }
142
+ if params[:azure_network_name]
143
+ xml.VirtualNetworkName params[:azure_network_name]
144
+ end
137
145
  }
138
146
  end
139
147
  builder.doc.at_css('Role') << roleXML.at_css('PersistentVMRole').children.to_s
@@ -120,7 +120,12 @@ class Azure
120
120
  xml.ServiceName params[:azure_dns_name]
121
121
  xml.Label Base64.encode64(params[:azure_dns_name])
122
122
  xml.Description 'Explicitly created hosted service'
123
- xml.Location params[:azure_service_location] || 'West US'
123
+ unless params[:azure_service_location].nil?
124
+ xml.Location params[:azure_service_location]
125
+ end
126
+ unless params[:azure_affinity_group].nil?
127
+ xml.AffinityGroup params[:azure_affinity_group]
128
+ end
124
129
  }
125
130
  end
126
131
  @connection.query_azure("hostedservices", "post", builder.to_xml)
@@ -51,12 +51,13 @@ end
51
51
  class Azure
52
52
  class Image
53
53
  attr_accessor :category, :label
54
- attr_accessor :name, :os, :eula, :description
54
+ attr_accessor :name, :os, :eula, :description, :location
55
55
  def initialize(image)
56
56
  @category = image.at_css('Category').content
57
57
  @label = image.at_css('Label').content
58
58
  @name = image.at_css('Name').content
59
59
  @os = image.at_css('OS').content
60
+ @location = image.at_css('Location').content.gsub(";", ", ") if image.at_css('Location')
60
61
  @eula = image.at_css('Eula').content if image.at_css('Eula')
61
62
  @description = image.at_css('Description').content if image.at_css('Description')
62
63
  end
@@ -28,10 +28,11 @@ module AzureAPI
28
28
  @host_name = params[:azure_api_host_name]
29
29
  @verify_ssl = params[:verify_ssl_cert]
30
30
  end
31
- def query_azure(service_name, verb = 'get', body = '')
31
+ def query_azure(service_name, verb = 'get', body = '', params = '')
32
32
  request_url = "https://#{@host_name}/#{@subscription_id}/services/#{service_name}"
33
33
  print '.'
34
34
  uri = URI.parse(request_url)
35
+ uri.query = params
35
36
  http = http_setup(uri)
36
37
  request = request_setup(uri, verb, body)
37
38
  response = http.request(request)
@@ -72,7 +73,7 @@ module AzureAPI
72
73
  elsif verb == 'delete'
73
74
  request = Net::HTTP::Delete.new(uri.request_uri)
74
75
  end
75
- request["x-ms-version"] = "2012-03-01"
76
+ request["x-ms-version"] = "2013-03-01"
76
77
  request["content-type"] = "application/xml"
77
78
  request["accept"] = "application/xml"
78
79
  request["accept-charset"] = "utf-8"
@@ -119,7 +119,12 @@ class Azure
119
119
  break if @connection.query_azure(servicecall, "get").search("AttachedTo").text == ""
120
120
  if attempt == 12 then puts "The associated disk could not be deleted due to time out." else sleep 25 end
121
121
  end
122
- @connection.query_azure(servicecall, "delete")
122
+
123
+ unless params[:preserve_azure_vhd]
124
+ @connection.query_azure(servicecall, 'delete', '', 'comp=media')
125
+ else
126
+ @connection.query_azure(servicecall, 'delete')
127
+ end
123
128
 
124
129
  if params[:delete_azure_storage_account]
125
130
  storage_account_name = xml_content(storage_account, "MediaLink")
@@ -222,7 +227,7 @@ class Azure
222
227
  xml.AdminPassword params[:admin_password]
223
228
  xml.ResetPasswordOnFirstLogon 'false'
224
229
  xml.EnableAutomaticUpdates 'false'
225
-
230
+ xml.AdminUsername params[:winrm_user]
226
231
  }
227
232
  end
228
233
 
@@ -248,18 +253,24 @@ class Azure
248
253
  if params[:tcp_endpoints]
249
254
  params[:tcp_endpoints].split(',').each do |endpoint|
250
255
  ports = endpoint.split(':')
251
- xml.InputEndpoint {
252
- xml.LocalPort ports[0]
253
- xml.Name 'tcpport_' + ports[0] + '_' + params[:azure_vm_name]
254
- if ports.length > 1
255
- xml.Port ports[1]
256
- else
257
- xml.Port ports[0]
258
- end
259
- xml.Protocol 'TCP'
260
- }
256
+ if !(ports.length > 1 && ports[1] == params[:port] || ports.length == 1 && ports[0] == params[:port])
257
+ xml.InputEndpoint {
258
+ xml.LocalPort ports[0]
259
+ xml.Name 'tcpport_' + ports[0] + '_' + params[:azure_vm_name]
260
+ if ports.length > 1
261
+ xml.Port ports[1]
262
+ else
263
+ xml.Port ports[0]
264
+ end
265
+ xml.Protocol 'TCP'
266
+ }
267
+ else
268
+ warn_message = ports.length > 1 ? "#{ports.join(':')} because this ports are" : "#{ports[0]} because this port is"
269
+ puts("Skipping tcp-endpoints: #{warn_message} already in use by ssh/winrm endpoint in current VM.")
270
+ end
261
271
  end
262
272
  end
273
+
263
274
  if params[:udp_endpoints]
264
275
  params[:udp_endpoints].split(',').each do |endpoint|
265
276
  ports = endpoint.split(':')
@@ -276,8 +287,16 @@ class Azure
276
287
  end
277
288
  end
278
289
  }
290
+ if params[:azure_subnet_name]
291
+ xml.SubnetNames {
292
+ xml.SubnetName params[:azure_subnet_name]
293
+ }
294
+ end
279
295
  }
280
296
  }
297
+ if params[:azure_availability_set]
298
+ xml.AvailabilitySetName params[:azure_availability_set]
299
+ end
281
300
  xml.Label Base64.encode64(params[:azure_vm_name]).strip
282
301
  xml.OSVirtualHardDisk {
283
302
  disk_name = params[:azure_os_disk_name] || "disk_" + SecureRandom.uuid
@@ -70,6 +70,13 @@ class Azure
70
70
  @connection.query_azure('storageaccounts/' + storage.name, 'delete')
71
71
  end
72
72
  end
73
+
74
+ def delete(name)
75
+ if self.exists?(name)
76
+ servicecall = "storageservices/" + name
77
+ @connection.query_azure(servicecall, "delete")
78
+ end
79
+ end
73
80
  end
74
81
  end
75
82
 
@@ -99,7 +106,11 @@ class Azure
99
106
  xml.Label Base64.encode64(params[:azure_storage_account])
100
107
  xml.Description params[:azure_storage_account_description] || 'Explicitly created storage service'
101
108
  # Location defaults to 'West US'
102
- xml.Location params[:azure_service_location] || 'West US'
109
+ if params[:azure_affinity_group]
110
+ xml.AffinityGroup params[:azure_affinity_group]
111
+ else
112
+ xml.Location params[:azure_service_location] || 'West US'
113
+ end
103
114
  }
104
115
  end
105
116
  @connection.query_azure("storageservices", "post", builder.to_xml)
@@ -98,6 +98,15 @@ class Chef
98
98
  end
99
99
  end
100
100
 
101
+ def msg_server_summary(server)
102
+ puts "\n"
103
+ msg_pair('DNS Name', server.hostedservicename + ".cloudapp.net")
104
+ msg_pair('VM Name', server.name)
105
+ msg_pair('Size', server.size)
106
+ msg_pair('Public Ip Address', server.publicipaddress)
107
+ puts "\n"
108
+ end
109
+
101
110
  def validate!(keys=[:azure_subscription_id, :azure_mgmt_cert, :azure_api_host_name])
102
111
  errors = []
103
112
  if(locate_config_value(:azure_mgmt_cert) != nil)
@@ -44,7 +44,7 @@ class Chef
44
44
 
45
45
  validate!
46
46
 
47
- image_labels = !locate_config_value(:show_all_fields) ? ['Name', 'OS'] : ['Name', 'Category', 'Label', 'OS']
47
+ image_labels = !locate_config_value(:show_all_fields) ? ['Name', 'OS', 'Location'] : ['Name', 'Category', 'Label', 'OS', 'Location']
48
48
  image_list = image_labels.map {|label| ui.color(label, :bold)}
49
49
  items = connection.images.all
50
50
 
@@ -54,7 +54,7 @@ class Chef
54
54
  end
55
55
 
56
56
  puts "\n"
57
- puts h.list(image_list, :uneven_columns_across, !locate_config_value(:show_all_fields) ? 2 : 4)
57
+ puts h.list(image_list, :uneven_columns_across, !locate_config_value(:show_all_fields) ? 3 : 5)
58
58
 
59
59
  end
60
60
  end
@@ -20,6 +20,8 @@
20
20
 
21
21
  require 'chef/knife/azure_base'
22
22
  require 'chef/knife/winrm_base'
23
+ require 'securerandom'
24
+
23
25
  class Chef
24
26
  class Knife
25
27
  class AzureServerCreate < Knife
@@ -119,18 +121,23 @@ class Chef
119
121
  option :azure_vm_name,
120
122
  :long => "--azure-vm-name NAME",
121
123
  :description => "Required for advanced server-create option.
122
- Specifies the name for the virtual machine. The name must be unique within the deployment."
124
+ Specifies the name for the virtual machine. The name must be unique within the deployment. The azure vm name cannot be more than 15 characters long"
123
125
 
124
126
  option :azure_service_location,
125
127
  :short => "-m LOCATION",
126
128
  :long => "--azure-service-location LOCATION",
127
- :description => "Required. Specifies the geographic location - the name of the data center location that is valid for your subscription.
129
+ :description => "Required if not using an Affinity Group. Specifies the geographic location - the name of the data center location that is valid for your subscription.
128
130
  Eg: West US, East US, East Asia, Southeast Asia, North Europe, West Europe"
129
131
 
132
+ option :azure_affinity_group,
133
+ :short => "-a GROUP",
134
+ :long => "--azure-affinity-group GROUP",
135
+ :description => "Required if not using a Service Location. Specifies Affinity Group the VM should belong to."
136
+
130
137
  option :azure_dns_name,
131
138
  :short => "-d DNS_NAME",
132
139
  :long => "--azure-dns-name DNS_NAME",
133
- :description => "Required. The DNS prefix name that can be used to access the cloud service which is unique within Windows Azure.
140
+ :description => "The DNS prefix name that can be used to access the cloud service which is unique within Windows Azure. Default is 'azure-dns-any_random_text'(e.g: azure-dns-be9b0f6f-7dda-456f-b2bf-4e28a3bc0add).
134
141
  If you want to add new VM to an existing service/deployment, specify an exiting dns-name,
135
142
  along with --azure-connect-to-existing-dns option.
136
143
  Otherwise a new deployment is created. For example, if the DNS of cloud service is MyService you could access the cloud service
@@ -153,6 +160,10 @@ class Chef
153
160
  :description => "Optional. Size of virtual machine (ExtraSmall, Small, Medium, Large, ExtraLarge)",
154
161
  :default => 'Small'
155
162
 
163
+ option :azure_availability_set,
164
+ :long => "--azure-availability-set NAME",
165
+ :description => "Optional. Name of availability set to add virtual machine into."
166
+
156
167
  option :tcp_endpoints,
157
168
  :short => "-t PORT_LIST",
158
169
  :long => "--tcp-endpoints PORT_LIST",
@@ -170,6 +181,15 @@ class Chef
170
181
  :default => false,
171
182
  :description => "Set this flag to add the new VM to an existing deployment/service. Must give the name of the existing
172
183
  DNS correctly in the --dns-name option"
184
+
185
+ option :azure_network_name,
186
+ :long => "--azure-network-name NETWORK_NAME",
187
+ :description => "Optional. Specifies the network of virtual machine"
188
+
189
+ option :azure_subnet_name,
190
+ :long => "--azure-subnet-name SUBNET_NAME",
191
+ :description => "Optional. Specifies the subnet of virtual machine"
192
+
173
193
  option :identity_file,
174
194
  :long => "--identity-file FILENAME",
175
195
  :description => "SSH identity file for authentication, optional. It is the RSA private key path. Specify either ssh-password or identity-file"
@@ -195,6 +215,43 @@ class Chef
195
215
  (0...len).map{65.+(rand(25)).chr}.join
196
216
  end
197
217
 
218
+ def wait_until_virtual_machine_ready(total_wait_time_in_minutes = 15, retry_interval_in_seconds = 30)
219
+ print "\n#{ui.color('Waiting for virtual machine to be ready.', :magenta)}"
220
+ total_wait_time_in_seconds = total_wait_time_in_minutes * 60
221
+ max_polling_attempts = total_wait_time_in_seconds / retry_interval_in_seconds
222
+
223
+ wait_start_time = Time.now
224
+
225
+ vm_ready = check_if_virtual_machine_ready()
226
+ polling_attempts = 1
227
+ until vm_ready || polling_attempts >= max_polling_attempts
228
+ print '.'
229
+ sleep retry_interval_in_seconds
230
+ vm_ready = check_if_virtual_machine_ready()
231
+ polling_attempts += 1
232
+ end
233
+ if vm_ready
234
+ elapsed_time_in_minutes = ((Time.now - wait_start_time) / 60).round(2)
235
+ puts("vm ready after #{elapsed_time_in_minutes} minutes.")
236
+ else
237
+ raise "Virtual machine not ready after #{total_wait_time_in_minutes} minutes."
238
+ end
239
+ end
240
+
241
+ def check_if_virtual_machine_ready()
242
+ deploy = connection.deploys.queryDeploy(locate_config_value(:azure_dns_name))
243
+ role = deploy.find_role(locate_config_value(:azure_vm_name))
244
+ if role.nil?
245
+ raise "Could not find role - status unknown."
246
+ end
247
+ Chef::Log.debug("Role status is #{role.status.to_s}")
248
+ if "ReadyRole".eql? role.status.to_s
249
+ return true
250
+ else
251
+ return false
252
+ end
253
+ end
254
+
198
255
  def tcp_test_winrm(ip_addr, port)
199
256
  hostname = ip_addr
200
257
  socket = TCPSocket.new(hostname, port)
@@ -253,66 +310,87 @@ class Chef
253
310
 
254
311
  Chef::Log.info("creating...")
255
312
 
313
+ config[:azure_dns_name] = get_dns_name(locate_config_value(:azure_dns_name))
256
314
  if not locate_config_value(:azure_vm_name)
257
315
  config[:azure_vm_name] = locate_config_value(:azure_dns_name)
258
316
  end
259
317
 
318
+ remove_hosted_service_on_failure = locate_config_value(:azure_dns_name)
319
+ if connection.hosts.exists?(locate_config_value(:azure_dns_name))
320
+ remove_hosted_service_on_failure = nil
321
+ end
322
+ remove_storage_service_on_failure = locate_config_value(:azure_storage_account)
323
+
260
324
  #If Storage Account is not specified, check if the geographic location has one to re-use
261
325
  if not locate_config_value(:azure_storage_account)
262
326
  storage_accts = connection.storageaccounts.all
263
327
  storage = storage_accts.find { |storage_acct| storage_acct.location.to_s == locate_config_value(:azure_service_location) }
264
328
  if not storage
265
329
  config[:azure_storage_account] = [strip_non_ascii(locate_config_value(:azure_vm_name)), random_string].join.downcase
330
+ remove_storage_service_on_failure = config[:azure_storage_account]
266
331
  else
332
+ remove_storage_service_on_failure = nil
267
333
  config[:azure_storage_account] = storage.name.to_s
268
334
  end
269
335
  end
270
336
 
271
- server = connection.deploys.create(create_server_def)
272
- fqdn = server.publicipaddress
337
+ begin
338
+ server = connection.deploys.create(create_server_def)
339
+ fqdn = server.publicipaddress
340
+ wait_until_virtual_machine_ready()
341
+ rescue Exception => e
342
+ Chef::Log.error("Failed to create the server -- exception being rescued: #{e.to_s}")
343
+ cleanup_and_exit(remove_hosted_service_on_failure, remove_storage_service_on_failure)
344
+ end
345
+
346
+ msg_server_summary(server)
273
347
 
274
- puts("\n")
275
348
  if is_image_windows?
349
+ # Set distro to windows-chef-client-msi
350
+ config[:distro] = "windows-chef-client-msi" if (config[:distro].nil? || config[:distro] == "chef-full")
351
+
276
352
  if locate_config_value(:bootstrap_protocol) == 'ssh'
277
353
  port = server.sshport
278
- print "\n#{ui.color("Waiting for sshd on #{fqdn}:#{port}", :magenta)}"
354
+ print "#{ui.color("Waiting for sshd on #{fqdn}:#{port}", :magenta)}"
279
355
 
280
356
  print(".") until tcp_test_ssh(fqdn,port) {
281
357
  sleep @initial_sleep_delay ||= 10
282
358
  puts("done")
283
- }
359
+ }
284
360
 
285
361
  elsif locate_config_value(:bootstrap_protocol) == 'winrm'
286
362
  port = server.winrmport
287
363
 
288
- print "\n#{ui.color("Waiting for winrm on #{fqdn}:#{port}", :magenta)}"
364
+ print "#{ui.color("Waiting for winrm on #{fqdn}:#{port}", :magenta)}"
289
365
 
290
366
  print(".") until tcp_test_winrm(fqdn,port) {
291
367
  sleep @initial_sleep_delay ||= 10
292
368
  puts("done")
293
- }
294
-
369
+ }
295
370
  end
296
- sleep 15
371
+
372
+ puts("\n")
297
373
  bootstrap_for_windows_node(server,fqdn, port).run
298
374
  else
299
375
  unless server && server.publicipaddress && server.sshport
300
376
  Chef::Log.fatal("server not created")
301
- exit 1
302
- end
377
+ exit 1
378
+ end
303
379
 
304
- port = server.sshport
380
+ port = server.sshport
305
381
 
306
- print "\n#{ui.color("Waiting for sshd on #{fqdn}:#{port}", :magenta)}"
382
+ print "#{ui.color("Waiting for sshd on #{fqdn}:#{port}", :magenta)}"
307
383
 
308
- print(".") until tcp_test_ssh(fqdn,port) {
309
- sleep @initial_sleep_delay ||= 10
310
- puts("done")
311
- }
384
+ print(".") until tcp_test_ssh(fqdn,port) {
385
+ sleep @initial_sleep_delay ||= 10
386
+ puts("done")
387
+ }
312
388
 
313
- sleep 15
389
+ puts("\n")
314
390
  bootstrap_for_node(server,fqdn,port).run
315
391
  end
392
+
393
+ msg_server_summary(server)
316
394
  end
317
395
 
318
396
  def load_cloud_attributes_in_hints(server)
@@ -371,8 +449,8 @@ class Chef
371
449
  end
372
450
  bootstrap.name_args = [fqdn]
373
451
  bootstrap.config[:chef_node_name] = config[:chef_node_name] || server.name
374
- bootstrap.config[:encrypted_data_bag_secret] = config[:encrypted_data_bag_secret]
375
- bootstrap.config[:encrypted_data_bag_secret_file] = config[:encrypted_data_bag_secret_file]
452
+ bootstrap.config[:encrypted_data_bag_secret] = locate_config_value(:encrypted_data_bag_secret)
453
+ bootstrap.config[:encrypted_data_bag_secret_file] = locate_config_value(:encrypted_data_bag_secret_file)
376
454
  bootstrap_common_params(bootstrap, server)
377
455
  end
378
456
 
@@ -394,6 +472,8 @@ class Chef
394
472
  bootstrap.config[:environment] = locate_config_value(:environment)
395
473
  # may be needed for vpc_mode
396
474
  bootstrap.config[:host_key_verify] = config[:host_key_verify]
475
+ bootstrap.config[:secret] = locate_config_value(:secret)
476
+ bootstrap.config[:secret_file] = locate_config_value(:secret_file)
397
477
 
398
478
  # Load cloud attributes.
399
479
  load_cloud_attributes_in_hints(server)
@@ -406,8 +486,6 @@ class Chef
406
486
  :azure_subscription_id,
407
487
  :azure_mgmt_cert,
408
488
  :azure_api_host_name,
409
- :azure_dns_name,
410
- :azure_service_location,
411
489
  :azure_source_image,
412
490
  :azure_vm_size,
413
491
  ])
@@ -415,6 +493,13 @@ class Chef
415
493
  ui.error("Specify the VM name using --azure-vm-name option, since you are connecting to existing dns")
416
494
  exit 1
417
495
  end
496
+ if locate_config_value(:azure_service_location) && locate_config_value(:azure_affinity_group)
497
+ ui.error("Cannot specify both --azure_service_location and --azure_affinity_group, use one or the other.")
498
+ exit 1
499
+ elsif locate_config_value(:azure_service_location).nil? && locate_config_value(:azure_affinity_group).nil?
500
+ ui.error("Must specify either --azure_service_location or --azure_affinity_group.")
501
+ exit 1
502
+ end
418
503
  end
419
504
 
420
505
  def create_server_def
@@ -429,7 +514,12 @@ class Chef
429
514
  :tcp_endpoints => locate_config_value(:tcp_endpoints),
430
515
  :udp_endpoints => locate_config_value(:udp_endpoints),
431
516
  :bootstrap_proto => locate_config_value(:bootstrap_protocol),
432
- :azure_connect_to_existing_dns => locate_config_value(:azure_connect_to_existing_dns)
517
+ :azure_connect_to_existing_dns => locate_config_value(:azure_connect_to_existing_dns),
518
+ :winrm_user => locate_config_value(:winrm_user),
519
+ :azure_availability_set => locate_config_value(:azure_availability_set),
520
+ :azure_affinity_group => locate_config_value(:azure_affinity_group),
521
+ :azure_network_name => locate_config_value(:azure_network_name),
522
+ :azure_subnet_name => locate_config_value(:azure_subnet_name)
433
523
  }
434
524
  # If user is connecting a new VM to an existing dns, then
435
525
  # the VM needs to have a unique public port. Logic below takes care of this.
@@ -450,6 +540,14 @@ class Chef
450
540
  server_def[:os_type] = 'Windows'
451
541
  if not locate_config_value(:winrm_password) or not locate_config_value(:bootstrap_protocol)
452
542
  ui.error("WinRM Password and Bootstrapping Protocol are compulsory parameters")
543
+ exit 1
544
+ end
545
+ # We can specify the AdminUsername after API version 2013-03-01. However, in this API version,
546
+ # the AdminUsername is a required parameter.
547
+ # Also, the user name cannot be Administrator, Admin, Admin1 etc, for enhanced security (provided by Azure)
548
+ if locate_config_value(:winrm_user).nil? || locate_config_value(:winrm_user).downcase =~ /admin*/
549
+ ui.error("WinRM User is compulsory parameter and it cannot be named 'admin*'")
550
+ exit
453
551
  end
454
552
  server_def[:admin_password] = locate_config_value(:winrm_password)
455
553
  server_def[:bootstrap_proto] = locate_config_value(:bootstrap_protocol)
@@ -472,7 +570,32 @@ class Chef
472
570
  end
473
571
  server_def
474
572
  end
573
+
574
+ def cleanup_and_exit(remove_hosted_service_on_failure, remove_storage_service_on_failure)
575
+ if remove_hosted_service_on_failure
576
+ connection.hosts.delete(remove_hosted_service_on_failure)
577
+ end
578
+ if remove_storage_service_on_failure
579
+ connection.storageaccounts.delete(remove_storage_service_on_failure)
580
+ end
581
+ exit 1
582
+ end
583
+
584
+ private
585
+ # This is related to Windows VM's specifically and computer name
586
+ # length limits for legacy computer accounts
587
+ MAX_VM_NAME_CHARACTERS = 15
588
+
589
+ # generate a random dns_name if azure_dns_name is empty
590
+ def get_dns_name(azure_dns_name, prefix = "az-")
591
+ return azure_dns_name unless azure_dns_name.nil?
592
+ if locate_config_value(:azure_vm_name).nil?
593
+ azure_dns_name = prefix + SecureRandom.hex(( MAX_VM_NAME_CHARACTERS - prefix.length)/2)
594
+ else
595
+ azure_dns_name = prefix + locate_config_value(:azure_vm_name)
596
+ end
597
+ end
598
+
475
599
  end
476
600
  end
477
601
  end
478
-
@@ -38,6 +38,12 @@ class Chef
38
38
  :default => false,
39
39
  :description => "Preserve corresponding OS Disk"
40
40
 
41
+ option :preserve_azure_vhd,
42
+ :long => "--preserve-azure-vhd",
43
+ :boolean => true,
44
+ :default => false,
45
+ :description => "Preserve underlying VHD"
46
+
41
47
  option :purge,
42
48
  :short => "-P",
43
49
  :long => "--purge",
@@ -118,6 +124,7 @@ class Chef
118
124
  end
119
125
 
120
126
  connection.roles.delete(name, params = { :preserve_azure_os_disk => locate_config_value(:preserve_azure_os_disk),
127
+ :preserve_azure_vhd => locate_config_value(:preserve_azure_vhd),
121
128
  :preserve_azure_dns_name => locate_config_value(:preserve_azure_dns_name),
122
129
  :azure_dns_name => server.hostedservicename,
123
130
  :delete_azure_storage_account => locate_config_value(:delete_azure_storage_account) })
@@ -1,6 +1,6 @@
1
1
  module Knife
2
2
  module Azure
3
- VERSION = "1.1.2"
3
+ VERSION = "1.1.4.rc.0"
4
4
  MAJOR, MINOR, TINY = VERSION.split('.')
5
5
  end
6
6
  end
metadata CHANGED
@@ -1,8 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: knife-azure
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
5
- prerelease:
4
+ version: 1.1.4.rc.0
5
+ prerelease: 6
6
6
  platform: ruby
7
7
  authors:
8
8
  - Barry Davis
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-09-03 00:00:00.000000000 Z
13
+ date: 2013-11-06 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: chef
@@ -97,7 +97,7 @@ dependencies:
97
97
  requirement: !ruby/object:Gem::Requirement
98
98
  none: false
99
99
  requirements:
100
- - - ~>
100
+ - - ! '>='
101
101
  - !ruby/object:Gem::Version
102
102
  version: 1.5.5
103
103
  type: :runtime
@@ -105,7 +105,7 @@ dependencies:
105
105
  version_requirements: !ruby/object:Gem::Requirement
106
106
  none: false
107
107
  requirements:
108
- - - ~>
108
+ - - ! '>='
109
109
  - !ruby/object:Gem::Version
110
110
  version: 1.5.5
111
111
  - !ruby/object:Gem::Dependency
@@ -124,6 +124,22 @@ dependencies:
124
124
  - - ! '>='
125
125
  - !ruby/object:Gem::Version
126
126
  version: '0'
127
+ - !ruby/object:Gem::Dependency
128
+ name: mixlib-config
129
+ requirement: !ruby/object:Gem::Requirement
130
+ none: false
131
+ requirements:
132
+ - - ~>
133
+ - !ruby/object:Gem::Version
134
+ version: '2.0'
135
+ type: :development
136
+ prerelease: false
137
+ version_requirements: !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ~>
141
+ - !ruby/object:Gem::Version
142
+ version: '2.0'
127
143
  description: A plugin to Opscode knife for creating instances on the Microsoft Azure
128
144
  platform
129
145
  email: oss@opscode.com
@@ -166,7 +182,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
166
182
  version: '0'
167
183
  segments:
168
184
  - 0
169
- hash: 1596077527808051330
185
+ hash: 4192859344861281444
170
186
  required_rubygems_version: !ruby/object:Gem::Requirement
171
187
  none: false
172
188
  requirements: