knife-azure 1.3.0 → 1.4.0.rc.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.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NWQxYTRjNmM1YmEwZmU2M2E1Y2M3NGU2YzkzNzIyNTE4OTFkMzQwOQ==
4
+ NDRlZjIwNmQwNWU1NzVlYzA4NGI3MzNjZTJlNmZkMmNlZmEwZDZiNg==
5
5
  data.tar.gz: !binary |-
6
- MGFhNjQxYTFmZjVjMjFkODZiZGI4NDY2ZGFjOWYyMDFlNTNkOTI0ZA==
6
+ MDgxYzQ2YTU2NGFlZDA0ZmE3MmM0M2UzM2NkNzFlZGZkNWU3MzQyYg==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- MmZmM2Q0ZTIwODUxZTRiN2FlNmY5ZjFlZmUxMGFlZDFkMjhiODhhYjBkYmY0
10
- MGVlYzA5NjdkNmZhMTY0ZDY2N2E1ODU3M2M4NzAwYjFiMDlmY2Q2ZDM1YjQ0
11
- MzlkNjBjOWZiNGUxNTg1ZGE4ZDRmODcxNzRjODMyNTYyODcwOTQ=
9
+ MGIxZTg3ZDEzN2ZlNmIzMzhlYTRhZjdlYjY5OTVkMzRkZDNjMWYzZDBiZjFh
10
+ NzlhNjRlMTE3ZTQwMGUwYjQxNTc4NDY3ZDNhZDVkZThjMmFlODgwNGZiYWM0
11
+ MDY0YWRiMmQ5OTYwNjBmZGE3YWY3ZGU2OGFmMWRmZGNjOGM5YWE=
12
12
  data.tar.gz: !binary |-
13
- NTZhYmViMTFhNDk1N2RiZTE0OTk2NzIzZDU3YjQ4YTQ0NGRlZTc3MjhmNTdm
14
- NTVjMDZlOTNjMjJmYTYxZTcyNGU4MzVhY2UxMTUyYjgyOTc4YmViZjdiY2Yx
15
- OTgxZGJkYmRlZDhlZTdhODliNGQ4ZGZhMTY0NzhiOWYyM2JlZTE=
13
+ NTlkMTM2Mjc3NDQ3ZmNmNmZlOGQwYzk2MGJhM2EyYWIxMjljYzNiMzIxMWU2
14
+ NTY0NDE3MTk2MjBhOGVmZTA5MzY0YWZiMWJjY2E0MmJmNTBiY2IyMzNmZDlm
15
+ ZjQ4YWQxYTAxMDZlMWYwYjhiYTdmMGFhZTEwNTcxZjMzZWY2MzY=
data/README.md CHANGED
@@ -48,7 +48,7 @@ location in your knife.rb:
48
48
  $ knife azure server list
49
49
 
50
50
  # Create and bootstrap an Ubuntu VM over ssh
51
- $ knife azure server create -N MyNewNode --azure-vm-size Medium --I 8fcc3d_Ubuntu-12_04-amd64-30GB -m 'West US' --ssh-user myuser --identity-file ~/.ssh/myprivatekey_rsa
51
+ $ knife azure server create -N MyNewNode --azure-vm-size Medium -I b39f27a8b8c64d52b05eac6a62ebad85__Ubuntu-14_04_1-LTS-amd64-server-20140927-en-us-30GB -m 'West US' --ssh-user myuser --identity-file ~/.ssh/myprivatekey_rsa
52
52
 
53
53
  # Create and bootstrap a Windows VM over winrm
54
54
  $ knife azure server create --azure-dns-name MyNewServerName --azure-vm-size Medium --azure-source-image 8fcc3d_Win2012-amd64-30GB --azure-service-location 'West US' --winrm-user myuser --winrm-password 'mypassword' --bootstrap-protocol winrm --distro 'windows-chef-client-msi'
@@ -182,8 +182,8 @@ These options may also be configured from knife.rb, as in this example:
182
182
  knife[:identity_file]='/path/to/RSA/private/key'
183
183
  knife[:azure_storage_account]='auxpreview104'
184
184
  knife[:azure_os_disk_name]='disk107'
185
- knife[:tcp_endpoints]='66'
186
- knife[:udp_endpoints]='77,88,99'
185
+ knife[:tcp-endpoints]='80:80,3389:5678'
186
+ knife[:udp-endpoints]='123:123'
187
187
 
188
188
  #### Options for Bootstrapping a Windows Node in Azure
189
189
 
@@ -25,6 +25,10 @@ class Azure
25
25
  certificate = Certificate.new(@connection)
26
26
  certificate.create(params)
27
27
  end
28
+ def add(certificate_data, certificate_password, certificate_format, dns_name)
29
+ certificate = Certificate.new(@connection)
30
+ certificate.add_certificate certificate_data, certificate_password, certificate_format, dns_name
31
+ end
28
32
  end
29
33
  end
30
34
 
@@ -41,17 +45,7 @@ class Azure
41
45
  # public part of the key
42
46
  @cert_data = generate_public_key_certificate_data({:ssh_key => params[:identity_file],
43
47
  :ssh_key_passphrase => params[:identity_file_passphrase]})
44
- # Generate XML to call the API
45
- # Add certificate to the hosted service
46
- builder = Nokogiri::XML::Builder.new do |xml|
47
- xml.CertificateFile('xmlns'=>'http://schemas.microsoft.com/windowsazure') {
48
- xml.Data @cert_data
49
- xml.CertificateFormat 'pfx'
50
- xml.Password 'knifeazure'
51
- }
52
- end
53
- # Windows Azure API call
54
- @connection.query_azure("hostedservices/#{params[:azure_dns_name]}/certificates", "post", builder.to_xml)
48
+ add_certificate @cert_data, 'knifeazure', 'pfx', params[:azure_dns_name]
55
49
  # Return the fingerprint to be used while adding role
56
50
  @fingerprint
57
51
  end
@@ -83,5 +77,20 @@ class Azure
83
77
  # Encode the pfx format - upload this certificate
84
78
  Base64.strict_encode64(pfx.to_der)
85
79
  end
80
+
81
+ def add_certificate certificate_data, certificate_password, certificate_format, dns_name
82
+ # Generate XML to call the API
83
+ # Add certificate to the hosted service
84
+ builder = Nokogiri::XML::Builder.new do |xml|
85
+ xml.CertificateFile('xmlns'=>'http://schemas.microsoft.com/windowsazure') {
86
+ xml.Data certificate_data
87
+ xml.CertificateFormat certificate_format
88
+ xml.Password certificate_password
89
+ }
90
+ end
91
+ # Windows Azure API call
92
+ @connection.query_azure("hostedservices/#{dns_name}/certificates", "post", builder.to_xml)
93
+ end
94
+
86
95
  end
87
96
  end
data/lib/azure/deploy.rb CHANGED
@@ -78,6 +78,10 @@ class Azure
78
78
  if params[:identity_file]
79
79
  params[:fingerprint] = @connection.certificates.create(params)
80
80
  end
81
+ if params[:cert_path]
82
+ cert_data = File.read (params[:cert_path])
83
+ @connection.certificates.add cert_data, params[:cert_password], 'pfx', params[:azure_dns_name]
84
+ end
81
85
  params['deploy_name'] = get_deploy_name_for_hostedservice(params[:azure_dns_name])
82
86
 
83
87
  if params['deploy_name'] != nil
@@ -99,7 +103,7 @@ class Azure
99
103
  end
100
104
  def delete(rolename)
101
105
  end
102
-
106
+
103
107
  def queryDeploy(hostedservicename)
104
108
  deploy = Deploy.new(@connection)
105
109
  deploy.retrieve(hostedservicename)
data/lib/azure/image.rb CHANGED
@@ -23,23 +23,51 @@ class Azure
23
23
  end
24
24
  def load
25
25
  @images ||= begin
26
- images = Hash.new
26
+ osimages = self.get_images("OSImage") #get OSImages
27
+ vmimages = self.get_images("VMImage") #get VMImages
28
+
29
+ all_images = osimages.merge(vmimages)
30
+ end
31
+ end
32
+
33
+ def all
34
+ self.load.values
35
+ end
36
+
37
+ # img_type = OSImages or VMImage
38
+ def get_images(img_type)
39
+ images = Hash.new
40
+
41
+ if(img_type == "OSImage")
27
42
  response = @connection.query_azure('images')
28
- osimages = response.css('OSImage')
43
+ elsif(img_type == "VMImage")
44
+ response = @connection.query_azure('vmimages')
45
+ end
46
+
47
+ unless response.to_s.empty?
48
+ osimages = response.css(img_type)
49
+
29
50
  osimages.each do |image|
30
51
  item = Image.new(image)
31
52
  images[item.name] = item
32
53
  end
33
- images
34
54
  end
55
+
56
+ images
35
57
  end
36
58
 
37
- def all
38
- self.load.values
59
+ def is_os_image(image_name)
60
+ os_images = self.get_images("OSImage").values
61
+ os_images.detect {|img| img.name == image_name} ? true : false
62
+ end
63
+
64
+ def is_vm_image(image_name)
65
+ vm_images = self.get_images("VMImage").values
66
+ vm_images.detect {|img| img.name == image_name} ? true : false
39
67
  end
40
68
 
41
69
  def exists?(name)
42
- self.all.key?(name)
70
+ self.all.detect {|img| img.name == name} ? true : false
43
71
  end
44
72
 
45
73
  def find(name)
data/lib/azure/rest.rb CHANGED
@@ -97,7 +97,7 @@ module AzureAPI
97
97
  request = Net::HTTP::Put.new(uri.request_uri)
98
98
  end
99
99
  text = verb == 'put'
100
- request["x-ms-version"] = "2013-08-01"
100
+ request["x-ms-version"] = "2014-04-01"
101
101
  request["content-type"] = text ? "text/plain" : "application/xml"
102
102
  request["accept"] = "application/xml"
103
103
  request["accept-charset"] = "utf-8"
data/lib/azure/role.rb CHANGED
@@ -16,6 +16,7 @@
16
16
  # limitations under the License.
17
17
  #
18
18
  require 'securerandom'
19
+
19
20
  class Azure
20
21
  class Roles
21
22
  include AzureUtility
@@ -100,7 +101,7 @@ class Azure
100
101
  if alone_on_hostedservice(role)
101
102
  if !params[:preserve_azure_dns_name] && compmedia
102
103
  servicecall = "hostedservices/#{role.hostedservicename}"
103
- else
104
+ else
104
105
  servicecall = "hostedservices/#{role.hostedservicename}/deployments/#{role.deployname}"
105
106
  end
106
107
  else
@@ -127,7 +128,7 @@ class Azure
127
128
  for attempt in 0..12
128
129
  break if @connection.query_azure(servicecall, "get").search("AttachedTo").text == ""
129
130
  if attempt == 12 then puts "The associated disk could not be deleted due to time out." else sleep 25 end
130
- end
131
+ end
131
132
  unless params[:preserve_azure_vhd]
132
133
  @connection.query_azure(servicecall, 'delete', '', 'comp=media', wait=params[:wait])
133
134
  else
@@ -142,7 +143,7 @@ class Azure
142
143
  roles_using_same_service = find_roles_within_hostedservice(params[:azure_dns_name])
143
144
  if roles_using_same_service.size <= 1
144
145
  servicecall = "hostedservices/" + params[:azure_dns_name]
145
- @connection.query_azure(servicecall, "delete")
146
+ @connection.query_azure(servicecall, "delete")
146
147
  end
147
148
  end
148
149
  end
@@ -219,122 +220,176 @@ class Azure
219
220
  xml.RoleName {xml.text params[:azure_vm_name]}
220
221
  xml.OsVersion('i:nil' => 'true')
221
222
  xml.RoleType 'PersistentVMRole'
223
+ xml.VMImageName params[:azure_source_image] if params[:is_vm_image]
224
+
222
225
  xml.ConfigurationSets {
223
226
  if params[:os_type] == 'Linux'
224
-
225
227
  xml.ConfigurationSet('i:type' => 'LinuxProvisioningConfigurationSet') {
226
- xml.ConfigurationSetType 'LinuxProvisioningConfiguration'
227
- xml.HostName params[:azure_vm_name]
228
- xml.UserName params[:ssh_user]
229
- unless params[:identity_file].nil?
230
- xml.DisableSshPasswordAuthentication 'true'
231
- xml.SSH {
232
- xml.PublicKeys {
233
- xml.PublicKey {
234
- xml.Fingerprint params[:fingerprint].to_s.upcase
235
- xml.Path '/home/' + params[:ssh_user] + '/.ssh/authorized_keys'
228
+ xml.ConfigurationSetType 'LinuxProvisioningConfiguration'
229
+ xml.HostName params[:azure_vm_name]
230
+ xml.UserName params[:ssh_user]
231
+ unless params[:identity_file].nil?
232
+ xml.DisableSshPasswordAuthentication 'true'
233
+ xml.SSH {
234
+ xml.PublicKeys {
235
+ xml.PublicKey {
236
+ xml.Fingerprint params[:fingerprint].to_s.upcase
237
+ xml.Path '/home/' + params[:ssh_user] + '/.ssh/authorized_keys'
238
+ }
236
239
  }
237
- }
238
- }
239
- else
240
- xml.UserPassword params[:ssh_password]
241
- xml.DisableSshPasswordAuthentication 'false'
242
- end
240
+ }
241
+ else
242
+ xml.UserPassword params[:ssh_password]
243
+ xml.DisableSshPasswordAuthentication 'false'
244
+ end
243
245
  }
244
246
  elsif params[:os_type] == 'Windows'
245
247
  xml.ConfigurationSet('i:type' => 'WindowsProvisioningConfigurationSet') {
246
- xml.ConfigurationSetType 'WindowsProvisioningConfiguration'
247
- xml.ComputerName params[:azure_vm_name]
248
- xml.AdminPassword params[:admin_password]
249
- xml.ResetPasswordOnFirstLogon 'false'
250
- xml.EnableAutomaticUpdates 'false'
251
- xml.AdminUsername params[:winrm_user]
252
- if params[:bootstrap_proto].downcase == 'winrm'
253
- xml.WinRM {
254
- xml.Listeners {
255
- xml.Listener {
256
- xml.Protocol 'Http'
248
+ xml.ConfigurationSetType 'WindowsProvisioningConfiguration'
249
+ xml.ComputerName params[:azure_vm_name]
250
+ xml.AdminPassword params[:admin_password]
251
+ xml.ResetPasswordOnFirstLogon 'false'
252
+ xml.EnableAutomaticUpdates 'false'
253
+ if params[:bootstrap_proto].downcase == 'winrm'
254
+ if params[:ssl_cert_fingerprint]
255
+ xml.StoredCertificateSettings {
256
+ xml.CertificateSetting {
257
+ xml.StoreLocation "LocalMachine"
258
+ xml.StoreName "My"
259
+ xml.Thumbprint params[:ssl_cert_fingerprint]
260
+ }
261
+ }
262
+ end
263
+ xml.WinRM {
264
+ xml.Listeners {
265
+ if params[:ssl_cert_fingerprint]
266
+ xml.Listener {
267
+ xml.CertificateThumbprint params[:ssl_cert_fingerprint]
268
+ xml.Protocol 'Https'
269
+ }
270
+ else
271
+ xml.Listener {
272
+ xml.Protocol 'Http'
273
+ }
274
+ end
275
+ }
257
276
  }
258
- }
259
- }
260
- end
277
+ end
278
+ xml.AdminUsername params[:winrm_user]
261
279
  }
262
280
  end
263
281
 
264
- xml.ConfigurationSet('i:type' => 'NetworkConfigurationSet') {
265
- xml.ConfigurationSetType 'NetworkConfiguration'
266
- xml.InputEndpoints {
267
- if params[:bootstrap_proto].downcase == 'ssh'
268
- xml.InputEndpoint {
269
- xml.LocalPort '22'
270
- xml.Name 'SSH'
271
- xml.Port params[:port]
272
- xml.Protocol 'TCP'
273
- }
274
- elsif params[:bootstrap_proto].downcase == 'winrm' and params[:os_type] == 'Windows'
275
- xml.InputEndpoint {
282
+ xml.ConfigurationSet('i:type' => 'NetworkConfigurationSet') {
283
+ xml.ConfigurationSetType 'NetworkConfiguration'
284
+ xml.InputEndpoints {
285
+
286
+ if params[:os_type] == 'Windows' and (params[:bootstrap_proto].downcase == 'winrm' or params[:bootstrap_proto].downcase == 'cloud-api')
287
+ xml.InputEndpoint {
276
288
  xml.LocalPort '5985'
277
289
  xml.Name 'WinRM'
278
290
  xml.Port params[:port]
279
291
  xml.Protocol 'TCP'
280
292
  }
281
- end
282
-
283
- if params[:tcp_endpoints]
284
- params[:tcp_endpoints].split(',').each do |endpoint|
285
- ports = endpoint.split(':')
286
- if !(ports.length > 1 && ports[1] == params[:port] || ports.length == 1 && ports[0] == params[:port])
293
+ else
287
294
  xml.InputEndpoint {
288
- xml.LocalPort ports[0]
289
- xml.Name 'tcpport_' + ports[0] + '_' + params[:azure_vm_name]
290
- if ports.length > 1
291
- xml.Port ports[1]
295
+ xml.LocalPort '22'
296
+ xml.Name 'SSH'
297
+ xml.Port params[:port]
298
+ xml.Protocol 'TCP'
299
+ }
300
+ end
301
+
302
+ if params[:tcp_endpoints]
303
+ params[:tcp_endpoints].split(',').each do |endpoint|
304
+ ports = endpoint.split(':')
305
+ if !(ports.length > 1 && ports[1] == params[:port] || ports.length == 1 && ports[0] == params[:port])
306
+ xml.InputEndpoint {
307
+ xml.LocalPort ports[0]
308
+ xml.Name 'tcpport_' + ports[0] + '_' + params[:azure_vm_name]
309
+ if ports.length > 1
310
+ xml.Port ports[1]
311
+ else
312
+ xml.Port ports[0]
313
+ end
314
+ xml.Protocol 'TCP'
315
+ }
292
316
  else
293
- xml.Port ports[0]
317
+ warn_message = ports.length > 1 ? "#{ports.join(':')} because this ports are" : "#{ports[0]} because this port is"
318
+ puts("Skipping tcp-endpoints: #{warn_message} already in use by ssh/winrm endpoint in current VM.")
294
319
  end
295
- xml.Protocol 'TCP'
296
- }
297
- else
298
- warn_message = ports.length > 1 ? "#{ports.join(':')} because this ports are" : "#{ports[0]} because this port is"
299
- puts("Skipping tcp-endpoints: #{warn_message} already in use by ssh/winrm endpoint in current VM.")
320
+ end
300
321
  end
301
- end
302
- end
303
322
 
304
- if params[:udp_endpoints]
305
- params[:udp_endpoints].split(',').each do |endpoint|
306
- ports = endpoint.split(':')
307
- xml.InputEndpoint {
308
- xml.LocalPort ports[0]
309
- xml.Name 'udpport_' + ports[0] + '_' + params[:azure_vm_name]
310
- if ports.length > 1
311
- xml.Port ports[1]
312
- else
313
- xml.Port ports[0]
323
+ if params[:udp_endpoints]
324
+ params[:udp_endpoints].split(',').each do |endpoint|
325
+ ports = endpoint.split(':')
326
+ xml.InputEndpoint {
327
+ xml.LocalPort ports[0]
328
+ xml.Name 'udpport_' + ports[0] + '_' + params[:azure_vm_name]
329
+ if ports.length > 1
330
+ xml.Port ports[1]
331
+ else
332
+ xml.Port ports[0]
333
+ end
334
+ xml.Protocol 'UDP'
335
+ }
314
336
  end
315
- xml.Protocol 'UDP'
337
+ end
338
+ }
339
+ if params[:azure_subnet_name]
340
+ xml.SubnetNames {
341
+ xml.SubnetName params[:azure_subnet_name]
316
342
  }
317
343
  end
318
- end
319
344
  }
320
- if params[:azure_subnet_name]
321
- xml.SubnetNames {
322
- xml.SubnetName params[:azure_subnet_name]
323
- }
324
- end
325
- }
326
345
  }
346
+
327
347
  if params[:azure_availability_set]
328
348
  xml.AvailabilitySetName params[:azure_availability_set]
329
349
  end
350
+ # Azure resource extension support
351
+ if params[:bootstrap_proto] == 'cloud-api'
352
+ xml.ResourceExtensionReferences {
353
+ xml.ResourceExtensionReference {
354
+ xml.ReferenceName params[:chef_extension]
355
+ xml.Publisher params[:chef_extension_publisher]
356
+ xml.Name params[:chef_extension]
357
+ xml.Version params[:chef_extension_version]
358
+ xml.ResourceExtensionParameterValues {
359
+ if params[:chef_extension_public_param]
360
+ xml.ResourceExtensionParameterValue {
361
+ xml.Key "PublicParams"
362
+ xml.Value params[:chef_extension_public_param]
363
+ xml.Type "Public"
364
+ }
365
+ end
366
+ if params[:chef_extension_private_param]
367
+ xml.ResourceExtensionParameterValue {
368
+ xml.Key "PrivateParams"
369
+ xml.Value params[:chef_extension_private_param]
370
+ xml.Type "Private"
371
+ }
372
+ end
373
+ }
374
+ xml.State "Enable"
375
+ }
376
+ }
377
+ end
378
+
330
379
  xml.Label Base64.encode64(params[:azure_vm_name]).strip
331
- xml.OSVirtualHardDisk {
332
- disk_name = params[:azure_os_disk_name] || "disk_" + SecureRandom.uuid
333
- xml.DiskName disk_name
334
- xml.MediaLink 'http://' + params[:azure_storage_account] + '.blob.core.windows.net/vhds/' + disk_name + '.vhd'
335
- xml.SourceImageName params[:azure_source_image]
336
- }
380
+
381
+ #OSVirtualHardDisk not required in case azure_source_image is a VMImage
382
+ unless(params[:is_vm_image])
383
+ xml.OSVirtualHardDisk {
384
+ disk_name = params[:azure_os_disk_name] || "disk_" + SecureRandom.uuid
385
+ xml.DiskName disk_name
386
+ xml.MediaLink 'http://' + params[:azure_storage_account] + '.blob.core.windows.net/vhds/' + disk_name + '.vhd'
387
+ xml.SourceImageName params[:azure_source_image]
388
+ }
389
+ end
390
+
337
391
  xml.RoleSize params[:azure_vm_size]
392
+ xml.ProvisionGuestAgent true if params[:bootstrap_proto] == 'cloud-api'
338
393
  }
339
394
  end
340
395
  builder.doc
@@ -44,8 +44,8 @@ class Chef
44
44
 
45
45
  validate!
46
46
 
47
- image_labels = !locate_config_value(:show_all_fields) ? ['Name', 'OS', 'Location'] : ['Name', 'Category', 'Label', 'OS', 'Location']
48
- image_list = image_labels.map {|label| ui.color(label, :bold)}
47
+ image_labels = !locate_config_value(:show_all_fields) ? ['Name', 'OS', 'Location'] : ['Name', 'Category', 'Label', 'OS', 'Location']
48
+ image_list = image_labels.map {|label| ui.color(label, :bold)}
49
49
  items = connection.images.all
50
50
 
51
51
  image_items = image_labels.map {|item| item.downcase }
@@ -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) ? 3 : 5)
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
@@ -40,7 +40,6 @@ class Chef
40
40
 
41
41
  def load_winrm_deps
42
42
  require 'winrm'
43
- require 'em-winrm'
44
43
  require 'chef/knife/winrm'
45
44
  require 'chef/knife/bootstrap_windows_winrm'
46
45
  end
@@ -51,7 +50,7 @@ class Chef
51
50
 
52
51
  option :bootstrap_protocol,
53
52
  :long => "--bootstrap-protocol protocol",
54
- :description => "Protocol to bootstrap windows servers. options: winrm/ssh",
53
+ :description => "Protocol to bootstrap windows servers. options: 'winrm' or 'ssh' or 'cloud-api'.",
55
54
  :default => "winrm"
56
55
 
57
56
  option :chef_node_name,
@@ -62,7 +61,8 @@ class Chef
62
61
  option :ssh_user,
63
62
  :short => "-x USERNAME",
64
63
  :long => "--ssh-user USERNAME",
65
- :description => "The ssh username"
64
+ :description => "The ssh username",
65
+ :default => "root"
66
66
 
67
67
  option :ssh_password,
68
68
  :short => "-P PASSWORD",
@@ -167,12 +167,12 @@ class Chef
167
167
  option :tcp_endpoints,
168
168
  :short => "-t PORT_LIST",
169
169
  :long => "--tcp-endpoints PORT_LIST",
170
- :description => "Comma separated list of TCP local and public ports to open i.e. '80:80,433:5000'"
170
+ :description => "Comma-separated list of TCP local and public ports to open e.g. '80:80,433:5000'"
171
171
 
172
172
  option :udp_endpoints,
173
173
  :short => "-u PORT_LIST",
174
174
  :long => "--udp-endpoints PORT_LIST",
175
- :description => "Comma separated list of UDP local and public ports to open i.e. '80:80,433:5000'"
175
+ :description => "Comma-separated list of UDP local and public ports to open e.g. '80:80,433:5000'"
176
176
 
177
177
  option :azure_connect_to_existing_dns,
178
178
  :short => "-c",
@@ -213,6 +213,18 @@ class Chef
213
213
  :description => "A JSON string to be added to the first run of chef-client",
214
214
  :proc => lambda { |o| JSON.parse(o) }
215
215
 
216
+ option :thumbprint,
217
+ :long => "--thumbprint THUMBPRINT",
218
+ :description => "The thumprint of the ssl certificate"
219
+
220
+ option :cert_passphrase,
221
+ :long => "--cert-passphrase PASSWORD",
222
+ :description => "SSL Certificate Password"
223
+
224
+ option :cert_path,
225
+ :long => "--cert-path PATH",
226
+ :description => "SSL Certificate Path"
227
+
216
228
  def strip_non_ascii(string)
217
229
  string.gsub(/[^0-9a-z ]/i, '')
218
230
  end
@@ -222,6 +234,7 @@ class Chef
222
234
  end
223
235
 
224
236
  def wait_until_virtual_machine_ready(retry_interval_in_seconds = 30)
237
+
225
238
  vm_status = nil
226
239
  puts
227
240
 
@@ -230,6 +243,24 @@ class Chef
230
243
  if vm_status != :vm_status_ready
231
244
  wait_for_virtual_machine_state(:vm_status_ready, 15, retry_interval_in_seconds)
232
245
  end
246
+
247
+ msg_server_summary(get_role_server())
248
+
249
+ if locate_config_value(:bootstrap_protocol) == "cloud-api"
250
+ extension_status = wait_for_resource_extension_state(:wagent_provisioning, 5, retry_interval_in_seconds)
251
+
252
+ if extension_status != :extension_installing
253
+ extension_status = wait_for_resource_extension_state(:extension_installing, 5, retry_interval_in_seconds)
254
+ end
255
+
256
+ if extension_status != :extension_provisioning
257
+ extension_status = wait_for_resource_extension_state(:extension_provisioning, 10, retry_interval_in_seconds)
258
+ end
259
+
260
+ if extension_status != :extension_ready
261
+ wait_for_resource_extension_state(:extension_ready, 5, retry_interval_in_seconds)
262
+ end
263
+ end
233
264
  rescue Exception => e
234
265
  Chef::Log.error("#{e.to_s}")
235
266
  raise 'Verify connectivity to Azure and subscription resource limit compliance (e.g. maximum CPU core limits) and try again.'
@@ -265,6 +296,37 @@ class Chef
265
296
  vm_status
266
297
  end
267
298
 
299
+ def wait_for_resource_extension_state(extension_status_goal, total_wait_time_in_minutes, retry_interval_in_seconds)
300
+
301
+ extension_status_ordering = {:extension_status_not_detected => 0, :wagent_provisioning => 1, :extension_installing => 2, :extension_provisioning => 3, :extension_ready => 4}
302
+
303
+ status_description = {:extension_status_not_detected => 'any', :wagent_provisioning => 'wagent provisioning', :extension_installing => "installing", :extension_provisioning => "provisioning", :extension_ready => "ready" }
304
+
305
+ print ui.color("Waiting for Resource Extension to reach status '#{status_description[extension_status_goal]}'", :magenta)
306
+
307
+ max_polling_attempts = (total_wait_time_in_minutes * 60) / retry_interval_in_seconds
308
+ polling_attempts = 0
309
+
310
+ wait_start_time = Time.now
311
+
312
+ begin
313
+ extension_status = get_extension_status()
314
+ extension_ready = extension_status_ordering[extension_status[:status]] >= extension_status_ordering[extension_status_goal]
315
+ print '.'
316
+ sleep retry_interval_in_seconds if !extension_ready
317
+ polling_attempts += 1
318
+ end until extension_ready || polling_attempts >= max_polling_attempts
319
+
320
+ if ! extension_ready
321
+ raise Chef::Exceptions::CommandTimeout, "Resource extension state '#{status_description[extension_status_goal]}' not reached after #{total_wait_time_in_minutes} minutes. #{extension_status[:message]}"
322
+ end
323
+
324
+ elapsed_time_in_minutes = ((Time.now - wait_start_time) / 60).round(2)
325
+ print ui.color("Resource extension state '#{status_description[extension_status_goal]}' reached after #{elapsed_time_in_minutes} minutes.\n", :cyan)
326
+
327
+ extension_status[:status]
328
+ end
329
+
268
330
  def get_virtual_machine_status()
269
331
  role = get_role_server()
270
332
  unless role.nil?
@@ -280,6 +342,53 @@ class Chef
280
342
  return :vm_status_not_detected
281
343
  end
282
344
 
345
+ def get_extension_status()
346
+ deployment_name = connection.deploys.get_deploy_name_for_hostedservice(locate_config_value(:azure_dns_name))
347
+ deployment = connection.query_azure("hostedservices/#{locate_config_value(:azure_dns_name)}/deployments/#{deployment_name}")
348
+ extension_status = Hash.new
349
+
350
+ if deployment.at_css('Deployment Name') != nil
351
+ role_list_xml = deployment.css('RoleInstanceList RoleInstance')
352
+ role_list_xml.each do |role|
353
+ if role.at_css("RoleName").text == locate_config_value(:azure_vm_name)
354
+ lnx_waagent_fail_msg = "Failed to deserialize the status reported by the Guest Agent"
355
+ waagent_status_msg = role.at_css("GuestAgentStatus FormattedMessage Message").text
356
+
357
+ if role.at_css("GuestAgentStatus Status").text == "Ready"
358
+ extn_status = role.at_css("ResourceExtensionStatusList Status").text
359
+
360
+ Chef::Log.debug("Resource extension status is #{extn_status}")
361
+
362
+ if extn_status == "Installing"
363
+ extension_status[:status] = :extension_installing
364
+ extension_status[:message] = role.at_css("ResourceExtensionStatusList FormattedMessage Message").text
365
+ elsif extn_status == "NotReady"
366
+ extension_status[:status] = :extension_provisioning
367
+ extension_status[:message] = role.at_css("ResourceExtensionStatusList FormattedMessage Message").text
368
+ elsif extn_status == "Ready"
369
+ extension_status[:status] = :extension_ready
370
+ extension_status[:message] = role.at_css("ResourceExtensionStatusList FormattedMessage Message").text
371
+ else
372
+ extension_status[:status] = :extension_status_not_detected
373
+ end
374
+ # This fix is for linux waagent issue: api unable to deserialize the waagent status.
375
+ elsif role.at_css("GuestAgentStatus Status").text == "NotReady" and waagent_status_msg == lnx_waagent_fail_msg
376
+ extension_status[:status] = :extension_ready
377
+ else
378
+ extension_status[:status] = :wagent_provisioning
379
+ extension_status[:message] = role.at_css("GuestAgentStatus Message").text
380
+ end
381
+ else
382
+ extension_status[:status] = :extension_status_not_detected
383
+ end
384
+ end
385
+ else
386
+ extension_status[:status] = :extension_status_not_detected
387
+ end
388
+
389
+ return extension_status
390
+ end
391
+
283
392
  def get_role_server()
284
393
  deploy = connection.deploys.queryDeploy(locate_config_value(:azure_dns_name))
285
394
  deploy.find_role(locate_config_value(:azure_vm_name))
@@ -341,6 +450,8 @@ class Chef
341
450
  Chef::Log.info("validating...")
342
451
  validate!
343
452
 
453
+ ssh_override_winrm if %w(ssh cloud-api).include?(locate_config_value(:bootstrap_protocol)) and !is_image_windows?
454
+
344
455
  Chef::Log.info("creating...")
345
456
 
346
457
  config[:azure_dns_name] = get_dns_name(locate_config_value(:azure_dns_name))
@@ -352,7 +463,7 @@ class Chef
352
463
  if connection.hosts.exists?(locate_config_value(:azure_dns_name))
353
464
  remove_hosted_service_on_failure = nil
354
465
  end
355
-
466
+
356
467
  #If Storage Account is not specified, check if the geographic location has one to re-use
357
468
  if not locate_config_value(:azure_storage_account)
358
469
  storage_accts = connection.storageaccounts.all
@@ -369,14 +480,13 @@ class Chef
369
480
  remove_storage_service_on_failure = nil
370
481
  else
371
482
  remove_storage_service_on_failure = locate_config_value(:azure_storage_account)
372
- end
483
+ end
373
484
  end
374
485
 
375
486
  begin
376
487
  connection.deploys.create(create_server_def)
377
488
  wait_until_virtual_machine_ready()
378
489
  server = get_role_server()
379
- fqdn = server.publicipaddress
380
490
  rescue Exception => e
381
491
  Chef::Log.error("Failed to create the server -- exception being rescued: #{e.to_s}")
382
492
  backtrace_message = "#{e.class}: #{e}\n#{e.backtrace.join("\n")}"
@@ -386,6 +496,12 @@ class Chef
386
496
 
387
497
  msg_server_summary(server)
388
498
 
499
+ bootstrap_exec(server) unless locate_config_value(:bootstrap_protocol) == 'cloud-api'
500
+ end
501
+
502
+ def bootstrap_exec(server)
503
+ fqdn = server.publicipaddress
504
+
389
505
  if is_image_windows?
390
506
  # Set distro to windows-chef-client-msi
391
507
  config[:distro] = "windows-chef-client-msi" if (config[:distro].nil? || config[:distro] == "chef-full")
@@ -409,7 +525,7 @@ class Chef
409
525
  puts("done")
410
526
  }
411
527
  end
412
-
528
+
413
529
  puts("\n")
414
530
  bootstrap_for_windows_node(server,fqdn, port).run
415
531
  else
@@ -461,7 +577,6 @@ class Chef
461
577
  bootstrap
462
578
  end
463
579
 
464
-
465
580
  def bootstrap_for_windows_node(server, fqdn, port)
466
581
  if locate_config_value(:bootstrap_protocol) == 'winrm'
467
582
 
@@ -523,15 +638,31 @@ class Chef
523
638
  :azure_source_image,
524
639
  :azure_vm_size,
525
640
  ])
641
+
642
+ if locate_config_value(:winrm_password) and (locate_config_value(:winrm_password).length <= 6 and locate_config_value(:winrm_password).length >= 72)
643
+ ui.error("The supplied password must be 6-72 characters long and meet password complexity requirements")
644
+ exit 1
645
+ end
646
+
647
+ if locate_config_value(:ssh_password) and (locate_config_value(:ssh_password).length <= 6 and locate_config_value(:ssh_password).length >= 72)
648
+ ui.error("The supplied password must be 6-72 characters long and meet password complexity requirements")
649
+ exit 1
650
+ end
651
+
526
652
  if locate_config_value(:azure_connect_to_existing_dns) && locate_config_value(:azure_vm_name).nil?
527
653
  ui.error("Specify the VM name using --azure-vm-name option, since you are connecting to existing dns")
528
654
  exit 1
529
655
  end
530
656
  if locate_config_value(:azure_service_location) && locate_config_value(:azure_affinity_group)
531
- ui.error("Cannot specify both --azure_service_location and --azure_affinity_group, use one or the other.")
657
+ ui.error("Cannot specify both --azure-service-location and --azure-affinity-group, use one or the other.")
532
658
  exit 1
533
659
  elsif locate_config_value(:azure_service_location).nil? && locate_config_value(:azure_affinity_group).nil?
534
- ui.error("Must specify either --azure_service_location or --azure_affinity_group.")
660
+ ui.error("Must specify either --azure-service-location or --azure-affinity-group.")
661
+ exit 1
662
+ end
663
+
664
+ if !(connection.images.exists?(locate_config_value(:azure_source_image)))
665
+ ui.error("Image provided is invalid")
535
666
  exit 1
536
667
  end
537
668
  end
@@ -553,58 +684,101 @@ class Chef
553
684
  :azure_availability_set => locate_config_value(:azure_availability_set),
554
685
  :azure_affinity_group => locate_config_value(:azure_affinity_group),
555
686
  :azure_network_name => locate_config_value(:azure_network_name),
556
- :azure_subnet_name => locate_config_value(:azure_subnet_name)
687
+ :azure_subnet_name => locate_config_value(:azure_subnet_name),
688
+ :ssl_cert_fingerprint => locate_config_value(:thumbprint),
689
+ :cert_path => locate_config_value(:cert_path),
690
+ :cert_password => locate_config_value(:cert_passphrase)
557
691
  }
558
692
  # If user is connecting a new VM to an existing dns, then
559
693
  # the VM needs to have a unique public port. Logic below takes care of this.
560
694
  if !is_image_windows? or locate_config_value(:bootstrap_protocol) == 'ssh'
561
695
  port = locate_config_value(:ssh_port) || '22'
562
696
  if locate_config_value(:azure_connect_to_existing_dns) && (port == '22')
563
- port = Random.rand(64000) + 1000
697
+ port = Random.rand(64000) + 1000
564
698
  end
565
699
  else
566
700
  port = locate_config_value(:winrm_port) || '5985'
567
701
  if locate_config_value(:azure_connect_to_existing_dns) && (port == '5985')
568
- port = Random.rand(64000) + 1000
702
+ port = Random.rand(64000) + 1000
569
703
  end
570
704
  end
705
+
571
706
  server_def[:port] = port
572
707
 
708
+ if locate_config_value(:bootstrap_protocol) == 'cloud-api'
709
+ server_def[:chef_extension] = get_chef_extension_name
710
+ server_def[:chef_extension_publisher] = get_chef_extension_publisher
711
+ server_def[:chef_extension_version] = get_chef_extension_version
712
+ server_def[:chef_extension_public_param] = get_chef_extension_public_params
713
+ server_def[:chef_extension_private_param] = get_chef_extension_private_params
714
+ else
715
+ if is_image_windows?
716
+ if not locate_config_value(:winrm_password) or not locate_config_value(:bootstrap_protocol)
717
+ ui.error("WinRM Password and Bootstrapping Protocol are compulsory parameters")
718
+ exit 1
719
+ end
720
+ # We can specify the AdminUsername after API version 2013-03-01. However, in this API version,
721
+ # the AdminUsername is a required parameter.
722
+ # Also, the user name cannot be Administrator, Admin, Admin1 etc, for enhanced security (provided by Azure)
723
+ if locate_config_value(:winrm_user).nil? || locate_config_value(:winrm_user).downcase =~ /admin*/
724
+ ui.error("WinRM User is compulsory parameter and it cannot be named 'admin*'")
725
+ exit 1
726
+ end
727
+ else
728
+ if not locate_config_value(:ssh_user)
729
+ ui.error("SSH User is compulsory parameter")
730
+ exit 1
731
+ end
732
+ unless locate_config_value(:ssh_password) or locate_config_value(:identity_file)
733
+ ui.error("Specify either SSH Key or SSH Password")
734
+ exit 1
735
+ end
736
+ end
737
+ end
573
738
  if is_image_windows?
574
739
  server_def[:os_type] = 'Windows'
575
- if not locate_config_value(:winrm_password) or not locate_config_value(:bootstrap_protocol)
576
- ui.error("WinRM Password and Bootstrapping Protocol are compulsory parameters")
577
- exit 1
578
- end
579
- # We can specify the AdminUsername after API version 2013-03-01. However, in this API version,
580
- # the AdminUsername is a required parameter.
581
- # Also, the user name cannot be Administrator, Admin, Admin1 etc, for enhanced security (provided by Azure)
582
- if locate_config_value(:winrm_user).nil? || locate_config_value(:winrm_user).downcase =~ /admin*/
583
- ui.error("WinRM User is compulsory parameter and it cannot be named 'admin*'")
584
- exit
585
- end
586
740
  server_def[:admin_password] = locate_config_value(:winrm_password)
587
741
  server_def[:bootstrap_proto] = locate_config_value(:bootstrap_protocol)
588
742
  else
589
743
  server_def[:os_type] = 'Linux'
590
- server_def[:bootstrap_proto] = 'ssh'
591
- if not locate_config_value(:ssh_user)
592
- ui.error("SSH User is compulsory parameter")
593
- exit 1
594
- end
595
- unless locate_config_value(:ssh_password) or locate_config_value(:identity_file)
596
- ui.error("Specify either SSH Key or SSH Password")
597
- exit 1
598
- end
599
-
744
+ server_def[:bootstrap_proto] = locate_config_value(:bootstrap_protocol) || 'ssh'
600
745
  server_def[:ssh_user] = locate_config_value(:ssh_user)
601
746
  server_def[:ssh_password] = locate_config_value(:ssh_password)
602
747
  server_def[:identity_file] = locate_config_value(:identity_file)
603
748
  server_def[:identity_file_passphrase] = locate_config_value(:identity_file_passphrase)
604
749
  end
750
+
751
+ server_def[:is_vm_image] = connection.images.is_vm_image(locate_config_value(:azure_source_image))
605
752
  server_def
606
753
  end
607
754
 
755
+ def get_chef_extension_name
756
+ extension_name = is_image_windows? ? "ChefClient" : "LinuxChefClient"
757
+ end
758
+
759
+ def get_chef_extension_publisher
760
+ publisher = "Chef.Bootstrap.WindowsAzure"
761
+ end
762
+
763
+ # get latest version
764
+ def get_chef_extension_version
765
+ extensions = @connection.query_azure("resourceextensions/#{get_chef_extension_publisher}/#{get_chef_extension_name}")
766
+ extensions.css("Version").max.text.to_f
767
+ end
768
+
769
+ def get_chef_extension_public_params
770
+ pub_config = Hash.new
771
+ pub_config[:client_rb] = "chef_server_url \t #{Chef::Config[:chef_server_url].to_json}\nvalidation_client_name\t#{Chef::Config[:validation_client_name].to_json}"
772
+ pub_config[:runlist] = locate_config_value(:run_list).empty? ? "" : locate_config_value(:run_list).join(",").to_json
773
+ Base64.encode64(pub_config.to_json)
774
+ end
775
+
776
+ def get_chef_extension_private_params
777
+ pri_config = Hash.new
778
+ pri_config[:validation_key] = File.read(Chef::Config[:validation_key])
779
+ Base64.encode64(pri_config.to_json)
780
+ end
781
+
608
782
  def cleanup_and_exit(remove_hosted_service_on_failure, remove_storage_service_on_failure)
609
783
  ui.warn("Cleaning up resources...")
610
784
 
@@ -619,8 +793,32 @@ class Chef
619
793
  end
620
794
  exit 1
621
795
  end
622
-
796
+
623
797
  private
798
+
799
+ def ssh_override_winrm
800
+ # unchanged ssh_user and changed winrm_user, override ssh_user
801
+ if locate_config_value(:ssh_user).eql?(options[:ssh_user][:default]) &&
802
+ !locate_config_value(:winrm_user).eql?(options[:winrm_user][:default])
803
+ config[:ssh_user] = locate_config_value(:winrm_user)
804
+ end
805
+ # unchanged ssh_port and changed winrm_port, override ssh_port
806
+ if locate_config_value(:ssh_port).eql?(options[:ssh_port][:default]) &&
807
+ !locate_config_value(:winrm_port).eql?(options[:winrm_port][:default])
808
+ config[:ssh_port] = locate_config_value(:winrm_port)
809
+ end
810
+ # unset ssh_password and set winrm_password, override ssh_password
811
+ if locate_config_value(:ssh_password).nil? &&
812
+ !locate_config_value(:winrm_password).nil?
813
+ config[:ssh_password] = locate_config_value(:winrm_password)
814
+ end
815
+ # unset identity_file and set kerberos_keytab_file, override identity_file
816
+ if locate_config_value(:identity_file).nil? &&
817
+ !locate_config_value(:kerberos_keytab_file).nil?
818
+ config[:identity_file] = locate_config_value(:kerberos_keytab_file)
819
+ end
820
+ end
821
+
624
822
  # This is related to Windows VM's specifically and computer name
625
823
  # length limits for legacy computer accounts
626
824
  MAX_VM_NAME_CHARACTERS = 15
@@ -631,10 +829,9 @@ class Chef
631
829
  if locate_config_value(:azure_vm_name).nil?
632
830
  azure_dns_name = prefix + SecureRandom.hex(( MAX_VM_NAME_CHARACTERS - prefix.length)/2)
633
831
  else
634
- azure_dns_name = prefix + locate_config_value(:azure_vm_name)
832
+ azure_dns_name = locate_config_value(:azure_vm_name)
635
833
  end
636
834
  end
637
-
638
835
  end
639
836
  end
640
837
  end
@@ -1,6 +1,6 @@
1
1
  module Knife
2
2
  module Azure
3
- VERSION = "1.3.0"
3
+ VERSION = "1.4.0.rc.0"
4
4
  MAJOR, MINOR, TINY = VERSION.split('.')
5
5
  end
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: knife-azure
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.4.0.rc.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Barry Davis
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-07-31 00:00:00.000000000 Z
12
+ date: 2014-12-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: nokogiri
@@ -31,14 +31,14 @@ dependencies:
31
31
  requirements:
32
32
  - - ! '>='
33
33
  - !ruby/object:Gem::Version
34
- version: 0.5.14
34
+ version: 0.8.2
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
39
  - - ! '>='
40
40
  - !ruby/object:Gem::Version
41
- version: 0.5.14
41
+ version: 0.8.2
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: chef
44
44
  requirement: !ruby/object:Gem::Requirement
@@ -81,6 +81,20 @@ dependencies:
81
81
  - - ~>
82
82
  - !ruby/object:Gem::Version
83
83
  version: 0.2.9
84
+ - !ruby/object:Gem::Dependency
85
+ name: knife-cloud
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ! '>='
89
+ - !ruby/object:Gem::Version
90
+ version: 1.0.0
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ! '>='
96
+ - !ruby/object:Gem::Version
97
+ version: 1.0.0
84
98
  description: A plugin to the Chef knife tool for creating instances on the Microsoft
85
99
  Azure platform
86
100
  email: oss@getchef.com