knife-azure 1.1.4 → 1.2.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.
@@ -18,6 +18,7 @@
18
18
 
19
19
  class Azure
20
20
  class StorageAccounts
21
+ include AzureUtility
21
22
  def initialize(connection)
22
23
  @connection=connection
23
24
  end
@@ -52,8 +53,9 @@ class Azure
52
53
  # Look up on cloud and not local cache
53
54
  def exists_on_cloud?(name)
54
55
  ret_val = @connection.query_azure("storageservices/#{name}")
55
- if ret_val.nil? || ret_val.css('Error Code').length > 0
56
- Chef::Log.warn 'Unable to find storage account:' + ret_val.at_css('Error Code').content + ' : ' + ret_val.at_css('Error Message').content if ret_val
56
+ error_code, error_message = error_from_response_xml(ret_val) if ret_val
57
+ if ret_val.nil? || error_code.length > 0
58
+ Chef::Log.warn 'Unable to find storage account:' + error_message + ' : ' + error_message if ret_val
57
59
  false
58
60
  else
59
61
  true
data/lib/azure/utility.rb CHANGED
@@ -25,5 +25,17 @@ module AzureUtility
25
25
  end
26
26
  content
27
27
  end
28
+
29
+ def error_from_response_xml(response_xml)
30
+ error_code_and_message = ['','']
31
+ error_node = response_xml.at_css('Error')
32
+
33
+ if error_node
34
+ error_code_and_message[0] = xml_content(error_node, 'Code')
35
+ error_code_and_message[1] = xml_content(error_node, 'Message')
36
+ end
37
+
38
+ error_code_and_message
39
+ end
28
40
  end
29
41
 
data/lib/azure/vnet.rb ADDED
@@ -0,0 +1,89 @@
1
+ #
2
+ # Author:: Jeff Mendoza (jeffmendoza@live.com)
3
+ # Copyright:: Copyright (c) 2013 Opscode, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ class Azure
20
+ class Vnets
21
+ def initialize(connection)
22
+ @connection = connection
23
+ end
24
+
25
+ def load
26
+ @vnets ||= begin
27
+ @vnets = {}
28
+ response = @connection.query_azure('networking/virtualnetwork')
29
+ response.css('VirtualNetworkSite').each do |vnet|
30
+ item = Vnet.new(@connection).parse(vnet)
31
+ @vnets[item.name] = item
32
+ end
33
+ @vnets
34
+ end
35
+ end
36
+
37
+ def all
38
+ load.values
39
+ end
40
+
41
+ def exists?(name)
42
+ load.key?(name)
43
+ end
44
+
45
+ def find(name)
46
+ load[name]
47
+ end
48
+
49
+ def create(params)
50
+ ag = Vnet.new(@connection)
51
+ ag.create(params)
52
+ end
53
+ end
54
+ end
55
+
56
+ class Azure
57
+ class Vnet
58
+ attr_accessor :name, :affinity_group, :state
59
+
60
+ def initialize(connection)
61
+ @connection = connection
62
+ end
63
+
64
+ def parse(image)
65
+ @name = image.at_css('Name').content
66
+ @affinity_group = image.at_css('AffinityGroup').content
67
+ @state = image.at_css('State').content
68
+ self
69
+ end
70
+
71
+ def create(params)
72
+ response = @connection.query_azure('networking/media')
73
+ vnets = response.css('VirtualNetworkSite')
74
+ vnet = nil
75
+ vnets.each { |vn| vnet = vn if vn['name'] == params[:azure_vnet_name] }
76
+ add = vnet.nil?
77
+ vnet = Nokogiri::XML::Node.new('VirtualNetworkSite', response) if add
78
+ vnet['name'] = params[:azure_vnet_name]
79
+ vnet['AffinityGroup'] = params[:azure_ag_name]
80
+ addr_space = Nokogiri::XML::Node.new('AddressSpace', response)
81
+ addr_prefix = Nokogiri::XML::Node.new('AddressPrefix', response)
82
+ addr_prefix.content = params[:azure_address_space]
83
+ addr_space.children = addr_prefix
84
+ vnet.children = addr_space
85
+ vnets.last.add_next_sibling(vnet) if add
86
+ @connection.query_azure('networking/media', 'put', response.to_xml)
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,76 @@
1
+ #
2
+ # Author:: Jeff Mendoza (jeffmendoza@live.com)
3
+ # Copyright:: Copyright (c) 2013 Opscode, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require File.expand_path('../azure_base', __FILE__)
20
+
21
+ class Chef
22
+ class Knife
23
+ class AzureAgCreate < Knife
24
+ include Knife::AzureBase
25
+
26
+ banner 'knife azure ag create (options)'
27
+
28
+ option :azure_affinity_group,
29
+ :short => '-a GROUP',
30
+ :long => '--azure-affinity-group GROUP',
31
+ :description => 'Specifies new affinity group name.'
32
+
33
+ option :azure_ag_desc,
34
+ :long => '--azure-ag-desc DESC',
35
+ :description => 'Optional. Description for new affinity group.'
36
+
37
+ option :azure_service_location,
38
+ :short => '-m LOCATION',
39
+ :long => '--azure-service-location LOCATION',
40
+ :description => 'Specifies the geographic location - the name of '\
41
+ 'the data center location that is valid for your '\
42
+ 'subscription. Eg: West US, East US, '\
43
+ 'East Asia, Southeast Asia, North Europe, West Europe'
44
+
45
+ def run
46
+ $stdout.sync = true
47
+
48
+ Chef::Log.info('validating...')
49
+ validate!([:azure_subscription_id,
50
+ :azure_mgmt_cert,
51
+ :azure_api_host_name,
52
+ :azure_affinity_group,
53
+ :azure_service_location])
54
+
55
+ params = {
56
+ azure_ag_name: locate_config_value(:azure_affinity_group),
57
+ azure_ag_desc: locate_config_value(:azure_ag_desc),
58
+ azure_location: locate_config_value(:azure_service_location),
59
+ }
60
+
61
+ rsp = connection.ags.create(params)
62
+ print "\n"
63
+ if rsp.at_css('Status').nil?
64
+ if rsp.at_css('Code').nil? || rsp.at_css('Message').nil?
65
+ puts 'Unknown Error. try -VV'
66
+ else
67
+ puts "#{rsp.at_css('Code').content}: "\
68
+ "#{rsp.at_css('Message').content}"
69
+ end
70
+ else
71
+ puts "Creation status: #{rsp.at_css('Status').content}"
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,51 @@
1
+ #
2
+ # Author:: Jeff Mendoza (jeffmendoza@live.com)
3
+ # Copyright:: Copyright (c) 2013 Opscode, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require File.expand_path('../azure_base', __FILE__)
20
+
21
+ class Chef
22
+ class Knife
23
+ class AzureAgList < Knife
24
+ include Knife::AzureBase
25
+
26
+ deps { require 'highline' }
27
+
28
+ banner 'knife azure ag list (options)'
29
+
30
+ def hl
31
+ @highline ||= HighLine.new
32
+ end
33
+
34
+ def run
35
+ $stdout.sync = true
36
+
37
+ validate!
38
+
39
+ cols = %w{Name Location Description}
40
+
41
+ the_list = cols.map { |col| ui.color(col, :bold) }
42
+ connection.ags.all.each do |ag|
43
+ cols.each { |attr| the_list << ag.send(attr.downcase).to_s }
44
+ end
45
+
46
+ puts "\n"
47
+ puts hl.list(the_list, :uneven_columns_across, cols.size)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -103,7 +103,16 @@ class Chef
103
103
  msg_pair('DNS Name', server.hostedservicename + ".cloudapp.net")
104
104
  msg_pair('VM Name', server.name)
105
105
  msg_pair('Size', server.size)
106
+ msg_pair('Azure Source Image', locate_config_value(:azure_source_image))
107
+ msg_pair('Azure Service Location', locate_config_value(:azure_service_location))
106
108
  msg_pair('Public Ip Address', server.publicipaddress)
109
+ msg_pair('Private Ip Address', server.ipaddress)
110
+ msg_pair('SSH Port', server.sshport) unless server.sshport.nil?
111
+ msg_pair('WinRM Port', server.winrmport) unless server.winrmport.nil?
112
+ msg_pair('TCP Ports', server.tcpports) unless server.tcpports.nil? or server.tcpports.empty?
113
+ msg_pair('UDP Ports', server.udpports) unless server.udpports.nil? or server.udpports.empty?
114
+ msg_pair('Environment', locate_config_value(:environment) || '_default')
115
+ msg_pair('Runlist', locate_config_value(:run_list)) unless locate_config_value(:run_list).empty?
107
116
  puts "\n"
108
117
  end
109
118
 
@@ -215,41 +215,68 @@ class Chef
215
215
  (0...len).map{65.+(rand(25)).chr}.join
216
216
  end
217
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
218
+ def wait_until_virtual_machine_ready(retry_interval_in_seconds = 30)
219
+ vm_status = nil
220
+ puts
221
+
222
+ begin
223
+ vm_status = wait_for_virtual_machine_state(:vm_status_provisioning, 5, retry_interval_in_seconds)
224
+ if vm_status != :vm_status_ready
225
+ wait_for_virtual_machine_state(:vm_status_ready, 15, retry_interval_in_seconds)
232
226
  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.")
227
+ rescue Exception => e
228
+ Chef::Log.error("#{e.to_s}")
229
+ raise 'Verify connectivity to Azure and subscription resource limit compliance (e.g. maximum CPU core limits) and try again.'
230
+ end
231
+ end
232
+
233
+ def wait_for_virtual_machine_state(vm_status_goal, total_wait_time_in_minutes, retry_interval_in_seconds)
234
+ vm_status_ordering = {:vm_status_not_detected => 0, :vm_status_provisioning => 1, :vm_status_ready => 2}
235
+ vm_status_description = {:vm_status_not_detected => 'any', :vm_status_provisioning => 'provisioning', :vm_status_ready => 'ready'}
236
+
237
+ print ui.color("Waiting for virtual machine to reach status '#{vm_status_description[vm_status_goal]}'", :magenta)
238
+
239
+ total_wait_time_in_seconds = total_wait_time_in_minutes * 60
240
+ max_polling_attempts = total_wait_time_in_seconds / retry_interval_in_seconds
241
+ polling_attempts = 0
242
+
243
+ wait_start_time = Time.now
244
+
245
+ begin
246
+ vm_status = get_virtual_machine_status()
247
+ vm_ready = vm_status_ordering[vm_status] >= vm_status_ordering[vm_status_goal]
248
+ print '.'
249
+ sleep retry_interval_in_seconds if !vm_ready
250
+ polling_attempts += 1
251
+ end until vm_ready || polling_attempts >= max_polling_attempts
252
+
253
+ if ! vm_ready
254
+ raise Chef::Exceptions::CommandTimeout, "Virtual machine state '#{vm_status_description[vm_status_goal]}' not reached after #{total_wait_time_in_minutes} minutes."
255
+ end
256
+
257
+ elapsed_time_in_minutes = ((Time.now - wait_start_time) / 60).round(2)
258
+ print ui.color("vm state '#{vm_status_description[vm_status_goal]}' reached after #{elapsed_time_in_minutes} minutes.\n", :cyan)
259
+ vm_status
260
+ end
261
+
262
+ def get_virtual_machine_status()
263
+ role = get_role_server()
264
+ unless role.nil?
265
+ Chef::Log.debug("Role status is #{role.status.to_s}")
266
+ if "ReadyRole".eql? role.status.to_s
267
+ return :vm_status_ready
268
+ elsif "Provisioning".eql? role.status.to_s
269
+ return :vm_status_provisioning
236
270
  else
237
- raise "Virtual machine not ready after #{total_wait_time_in_minutes} minutes."
271
+ return :vm_status_not_detected
238
272
  end
273
+ end
274
+ return :vm_status_not_detected
239
275
  end
240
276
 
241
- def check_if_virtual_machine_ready()
277
+ def get_role_server()
242
278
  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
279
+ deploy.find_role(locate_config_value(:azure_vm_name))
253
280
  end
254
281
 
255
282
  def tcp_test_winrm(ip_addr, port)
@@ -335,11 +362,14 @@ class Chef
335
362
  end
336
363
 
337
364
  begin
338
- server = connection.deploys.create(create_server_def)
339
- fqdn = server.publicipaddress
365
+ connection.deploys.create(create_server_def)
340
366
  wait_until_virtual_machine_ready()
367
+ server = get_role_server()
368
+ fqdn = server.publicipaddress
341
369
  rescue Exception => e
342
370
  Chef::Log.error("Failed to create the server -- exception being rescued: #{e.to_s}")
371
+ backtrace_message = "#{e.class}: #{e}\n#{e.backtrace.join("\n")}"
372
+ Chef::Log.debug("#{backtrace_message}")
343
373
  cleanup_and_exit(remove_hosted_service_on_failure, remove_storage_service_on_failure)
344
374
  end
345
375
 
@@ -71,6 +71,12 @@ class Chef
71
71
  option :azure_dns_name,
72
72
  :long => "--azure-dns-name NAME",
73
73
  :description => "specifies the DNS name (also known as hosted service name)"
74
+
75
+ option :wait,
76
+ :long => "--wait",
77
+ :boolean => true,
78
+ :default => false,
79
+ :description => "Wait for server deletion. Default is false"
74
80
 
75
81
  # Extracted from Chef::Knife.delete_object, because it has a
76
82
  # confirmation step built in... By specifying the '--purge'
@@ -127,7 +133,8 @@ class Chef
127
133
  :preserve_azure_vhd => locate_config_value(:preserve_azure_vhd),
128
134
  :preserve_azure_dns_name => locate_config_value(:preserve_azure_dns_name),
129
135
  :azure_dns_name => server.hostedservicename,
130
- :delete_azure_storage_account => locate_config_value(:delete_azure_storage_account) })
136
+ :delete_azure_storage_account => locate_config_value(:delete_azure_storage_account),
137
+ :wait => locate_config_value(:wait) })
131
138
 
132
139
  puts "\n"
133
140
  ui.warn("Deleted server #{server.name}")
@@ -22,11 +22,11 @@ require File.expand_path('../azure_base', __FILE__)
22
22
 
23
23
  class Chef
24
24
  class Knife
25
- class AzureServerDescribe < Knife
25
+ class AzureServerShow < Knife
26
26
 
27
27
  include Knife::AzureBase
28
28
 
29
- banner "knife azure server describe ROLE [ROLE]"
29
+ banner "knife azure server show SERVER [SERVER]"
30
30
 
31
31
  def run
32
32
  $stdout.sync = true
@@ -38,27 +38,35 @@ class Chef
38
38
  puts ''
39
39
  if (role)
40
40
  details = Array.new
41
- details << ui.color('Role name', :bold, :blue)
41
+ details << ui.color('Role name', :bold, :cyan)
42
42
  details << role.name
43
- details << ui.color('Status', :bold, :blue)
43
+ details << ui.color('Status', :bold, :cyan)
44
44
  details << role.status
45
- details << ui.color('Size', :bold, :blue)
45
+ details << ui.color('Size', :bold, :cyan)
46
46
  details << role.size
47
- details << ui.color('Hosted service name', :bold, :blue)
47
+ details << ui.color('Hosted service name', :bold, :cyan)
48
48
  details << role.hostedservicename
49
- details << ui.color('Deployment name', :bold, :blue)
49
+ details << ui.color('Deployment name', :bold, :cyan)
50
50
  details << role.deployname
51
- details << ui.color('Host name', :bold, :blue)
51
+ details << ui.color('Host name', :bold, :cyan)
52
52
  details << role.hostname
53
- details << ui.color('SSH', :bold, :blue)
54
- details << role.sshipaddress + ':' + role.sshport
53
+ unless role.sshport.nil?
54
+ details << ui.color('SSH port', :bold, :cyan)
55
+ details << role.sshport
56
+ end
57
+ unless role.winrmport.nil?
58
+ details << ui.color('WinRM port', :bold, :cyan)
59
+ details << role.winrmport
60
+ end
61
+ details << ui.color('Public IP', :bold, :cyan)
62
+ details << role.publicipaddress
55
63
  puts ui.list(details, :columns_across, 2)
56
64
  if role.tcpports.length > 0 || role.udpports.length > 0
57
65
  details.clear
58
- details << ui.color('Ports open', :bold, :blue)
59
- details << ui.color('Local port', :bold, :blue)
60
- details << ui.color('IP', :bold, :blue)
61
- details << ui.color('Public port', :bold, :blue)
66
+ details << ui.color('Ports open', :bold, :cyan)
67
+ details << ui.color('Local port', :bold, :cyan)
68
+ details << ui.color('IP', :bold, :cyan)
69
+ details << ui.color('Public port', :bold, :cyan)
62
70
  if role.tcpports.length > 0
63
71
  role.tcpports.each do |port|
64
72
  details << 'tcp'