knife-azure 1.1.4 → 1.2.0

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