knife-azure 1.3.0 → 1.4.0.rc.0

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