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.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NmY2MDlmMjMwZTA5OWVlOGRmODI2MzJmYjBmOWJkNmEzMjU1NWMzZQ==
5
+ data.tar.gz: !binary |-
6
+ ZGM5ZGRiOWNhYWYwZWE1MzEyYmQxNTk2OTZlNGQwM2QzMjA3OWU4NQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ ZTBmY2UxNTFlNTE0NDkwNWUzMTgzNDQ4YjBmNWI5OWUyMzAwNjVmYzRkZWRk
10
+ YjNiZTQ4OGRkZDQ4OThlMDc3ZWMzZDJhZTc1YjNlZmJhMWZjOTk5NzEyMmJj
11
+ NzhiZTUwYWE5Njg5YjQ2YjFhNWIyZWZjMWI1ZmIyMTUzMjczNzc=
12
+ data.tar.gz: !binary |-
13
+ ZjgzYTM4OGJiZTQ2MDVkN2FmY2E1YmEyZDk4ODA0M2ZkZGVmM2MzMzE3NTM5
14
+ YTQxMzQwYjc0MDdmYmQ5ZGZiZjc5NWNmMTQwMTMwYzFlYzQ0NTkzMDgyOTc1
15
+ NWZjMmU2YmM3Y2ViZGJkOWRiYWRlYmJiYTQ0MDM0NmE1YmI4MWM=
data/README.md CHANGED
@@ -240,6 +240,43 @@ Outputs a list of all servers in the currently configured Azure account. PLEASE
240
240
 
241
241
  knife azure server list
242
242
 
243
+ ### Azure AG List Subcommand
244
+ Outputs a list of defined affinity groups in the azure subscription.
245
+
246
+ knife azure ag list
247
+
248
+ ### Azure AG Create Subcommand
249
+ Creates a new affinity group in the specified service location.
250
+
251
+ knife azure ag create -a 'mynewag' -m 'West US' --azure-ag-desc 'Optional Description'
252
+
253
+ Knife options:
254
+
255
+ :azure_affinity_group Specifies new affinity group name.
256
+ :azure_service_location Specifies the geographic location.
257
+ :azure_ag_desc Optional. Description for new affinity group.
258
+
259
+ ### Azure Vnet List Subcommand
260
+ Outputs a list of defined virtual networks in the azure subscription.
261
+
262
+ knife azure vnet list
263
+
264
+ ### Azure Vnet Create Subcommand
265
+ Creates a new or modifies an existing virtual network. If an existing virtual network is named, the
266
+ affinity group and address space are replaced with the new values.
267
+
268
+ knife azure vnet create -n 'mynewvn' -a 'existingag' --azure_address_space '10.0.0.0/24'
269
+
270
+ Knife options:
271
+
272
+ :azure_network_name Specifies the name of the virtual network to create.
273
+ :azure_affinity_group Specifies the affinity group to associate with the vnet.
274
+ :azure_address_space Specifies the address space of the vnet using CIDR notation.
275
+
276
+ For CIDR notation, see here: http://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing
277
+ Address available are defined in RFC 1918: http://en.wikipedia.org/wiki/Private_network
278
+
279
+
243
280
  ## Alternative Management Certificate Specification
244
281
  In addition to specifying the management certificate using the publishsettings
245
282
  file, you can also specify it in PEM format. Follow these steps to generate the certificate in the PEM format:
data/lib/azure/ag.rb ADDED
@@ -0,0 +1,97 @@
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 AGs
21
+ def initialize(connection)
22
+ @connection = connection
23
+ end
24
+
25
+ def load
26
+ @ags ||= begin
27
+ @ags = {}
28
+ response = @connection.query_azure('affinitygroups',
29
+ 'get',
30
+ '',
31
+ '',
32
+ false)
33
+ response.css('AffinityGroup').each do |ag|
34
+ item = AG.new(@connection).parse(ag)
35
+ @ags[item.name] = item
36
+ end
37
+ @ags
38
+ end
39
+ end
40
+
41
+ def all
42
+ load.values
43
+ end
44
+
45
+ def exists?(name)
46
+ load.key?(name)
47
+ end
48
+
49
+ def find(name)
50
+ load[name]
51
+ end
52
+
53
+ def create(params)
54
+ ag = AG.new(@connection)
55
+ ag.create(params)
56
+ end
57
+ end
58
+ end
59
+
60
+ class Azure
61
+ class AG
62
+ attr_accessor :name, :label, :description, :location
63
+
64
+ def initialize(connection)
65
+ @connection = connection
66
+ end
67
+
68
+ def parse(image)
69
+ @name = image.at_css('Name').content
70
+ @label = image.at_css('Label').content
71
+ @description = image.at_css('Description').content if
72
+ image.at_css('Description')
73
+ @location = image.at_css('Location').content if image.at_css('Location')
74
+ self
75
+ end
76
+
77
+ def create(params)
78
+ builder = Nokogiri::XML::Builder.new(encoding: 'utf-8') do |xml|
79
+ xml.CreateAffinityGroup(
80
+ xmlns: 'http://schemas.microsoft.com/windowsazure'
81
+ ) do
82
+ xml.Name params[:azure_ag_name]
83
+ xml.Label Base64.strict_encode64(params[:azure_ag_name])
84
+ unless params[:azure_ag_desc].nil?
85
+ xml.Description params[:azure_ag_desc]
86
+ end
87
+ xml.Location params[:azure_location]
88
+ end
89
+ end
90
+ @connection.query_azure('affinitygroups',
91
+ 'post',
92
+ builder.to_xml,
93
+ '',
94
+ false)
95
+ end
96
+ end
97
+ end
@@ -24,11 +24,15 @@ require File.expand_path('../role', __FILE__)
24
24
  require File.expand_path('../disk', __FILE__)
25
25
  require File.expand_path('../image', __FILE__)
26
26
  require File.expand_path('../certificate', __FILE__)
27
+ require File.expand_path('../ag', __FILE__)
28
+ require File.expand_path('../vnet', __FILE__)
27
29
 
28
30
  class Azure
29
31
  class Connection
30
32
  include AzureAPI
31
- attr_accessor :hosts, :rest, :images, :deploys, :roles, :disks, :storageaccounts, :certificates
33
+ include AzureUtility
34
+ attr_accessor :hosts, :rest, :images, :deploys, :roles,
35
+ :disks, :storageaccounts, :certificates, :ags, :vnets
32
36
  def initialize(params={})
33
37
  @rest = Rest.new(params)
34
38
  @hosts = Hosts.new(self)
@@ -38,19 +42,32 @@ class Azure
38
42
  @roles = Roles.new(self)
39
43
  @disks = Disks.new(self)
40
44
  @certificates = Certificates.new(self)
45
+ @ags = AGs.new(self)
46
+ @vnets = Vnets.new(self)
41
47
  end
42
- def query_azure(service_name, verb = 'get', body = '', params = '')
43
- Chef::Log.info 'calling ' + verb + ' ' + service_name
48
+
49
+ def query_azure(service_name,
50
+ verb = 'get',
51
+ body = '',
52
+ params = '',
53
+ wait = true,
54
+ services = true)
55
+ Chef::Log.info 'calling ' + verb + ' ' + service_name + (wait ? " synchronously" : " asynchronously")
44
56
  Chef::Log.debug body unless body == ''
45
- response = @rest.query_azure(service_name, verb, body, params)
57
+ response = @rest.query_azure(service_name, verb, body, params, services)
46
58
  if response.code.to_i == 200
47
59
  ret_val = Nokogiri::XML response.body
60
+ elsif !wait && response.code.to_i == 202
61
+ Chef::Log.debug 'Request accepted in asynchronous mode'
62
+ ret_val = Nokogiri::XML response.body
48
63
  elsif response.code.to_i >= 201 && response.code.to_i <= 299
49
64
  ret_val = wait_for_completion()
50
65
  else
51
66
  if response.body
52
67
  ret_val = Nokogiri::XML response.body
53
- Chef::Log.warn ret_val.at_css('Error Code').content + ' : ' + ret_val.at_css('Error Message').content
68
+ Chef::Log.debug ret_val.to_xml
69
+ error_code, error_message = error_from_response_xml(ret_val)
70
+ Chef::Log.warn error_code + ' : ' + error_message if error_code.length > 0
54
71
  else
55
72
  Chef::Log.warn 'http error: ' + response.code
56
73
  end
@@ -63,14 +80,15 @@ class Azure
63
80
  while status == 'InProgress'
64
81
  response = @rest.query_for_completion()
65
82
  ret_val = Nokogiri::XML response.body
66
- status = ret_val.at_css('Status').content
83
+ status = xml_content(ret_val,'Status')
67
84
  if status == 'InProgress'
68
85
  print '.'
69
86
  sleep(0.5)
70
87
  elsif status == 'Succeeded'
71
88
  Chef::Log.debug 'not InProgress : ' + ret_val.to_xml
72
89
  else
73
- Chef::Log.warn status + ret_val.at_css('Error Code').content + ' : ' + ret_val.at_css('Error Message').content
90
+ error_code, error_message = error_from_response_xml(ret_val)
91
+ Chef::Log.warn status + error_code + ' : ' + error_message if error_code.length > 0
74
92
  end
75
93
  end
76
94
  ret_val
data/lib/azure/deploy.rb CHANGED
@@ -18,6 +18,7 @@
18
18
 
19
19
  class Azure
20
20
  class Deploys
21
+ include AzureUtility
21
22
  def initialize(connection)
22
23
  @connection=connection
23
24
  end
@@ -65,8 +66,9 @@ class Azure
65
66
  end
66
67
  else
67
68
  ret_val = @connection.hosts.create(params)
68
- if ret_val.css('Error Code').length > 0
69
- Chef::Log.fatal 'Unable to create DNS:' + ret_val.at_css('Error Code').content + ' : ' + ret_val.at_css('Error Message').content
69
+ error_code, error_message = error_from_response_xml(ret_val)
70
+ if error_code.length > 0
71
+ Chef::Log.fatal 'Unable to create DNS:' + error_code + ' : ' + error_message
70
72
  exit 1
71
73
  end
72
74
  end
@@ -88,8 +90,10 @@ class Azure
88
90
  deployXML = deploy.setup(params)
89
91
  ret_val = deploy.create(params, deployXML)
90
92
  end
91
- if ret_val.css('Error Code').length > 0
92
- raise Chef::Log.fatal 'Unable to create role:' + ret_val.at_css('Error Code').content + ' : ' + ret_val.at_css('Error Message').content
93
+ error_code, error_message = error_from_response_xml(ret_val)
94
+ if error_code.length > 0
95
+ Chef::Log.debug(ret_val.to_s)
96
+ raise Chef::Log.fatal 'Unable to create role:' + error_code + ' : ' + error_message
93
97
  end
94
98
  @connection.roles.find_in_hosted_service(params[:azure_vm_name], params[:azure_dns_name])
95
99
  end
data/lib/azure/host.rb CHANGED
@@ -18,6 +18,7 @@
18
18
 
19
19
  class Azure
20
20
  class Hosts
21
+ include AzureUtility
21
22
  def initialize(connection)
22
23
  @connection=connection
23
24
  end
@@ -53,8 +54,9 @@ class Azure
53
54
  # Look up on cloud and not local cache
54
55
  def exists_on_cloud?(name)
55
56
  ret_val = @connection.query_azure("hostedservices/#{name}")
56
- if ret_val.nil? || ret_val.css('Error Code').length > 0
57
- Chef::Log.warn 'Unable to find hosted(cloud) service:' + ret_val.at_css('Error Code').content + ' : ' + ret_val.at_css('Error Message').content if ret_val
57
+ error_code, error_message = error_from_response_xml(ret_val) if ret_val
58
+ if ret_val.nil? || error_code.length > 0
59
+ Chef::Log.warn('Unable to find hosted(cloud) service:' + error_code + ' : ' + error_message) if ret_val
58
60
  false
59
61
  else
60
62
  true
@@ -70,8 +72,9 @@ class Azure
70
72
  # Look up hosted service on cloud and not local cache
71
73
  def fetch_from_cloud(name)
72
74
  ret_val = @connection.query_azure("hostedservices/#{name}")
73
- if ret_val.nil? || ret_val.css('Error Code').length > 0
74
- Chef::Log.warn 'Unable to find hosted(cloud) service:' + ret_val.at_css('Error Code').content + ' : ' + ret_val.at_css('Error Message').content if ret_val
75
+ error_code, error_message = error_from_response_xml(ret_val) if ret_val
76
+ if ret_val.nil? || error_code.length > 0
77
+ Chef::Log.warn('Unable to find hosted(cloud) service:' + error_code + ' : ' + error_message) if ret_val
75
78
  nil
76
79
  else
77
80
  Host.new(@connection).parse(ret_val)
data/lib/azure/rest.rb CHANGED
@@ -28,9 +28,26 @@ module AzureAPI
28
28
  @host_name = params[:azure_api_host_name]
29
29
  @verify_ssl = params[:verify_ssl_cert]
30
30
  end
31
- def query_azure(service_name, verb = 'get', body = '', params = '')
32
- request_url = "https://#{@host_name}/#{@subscription_id}/services/#{service_name}"
31
+
32
+ def query_azure(service_name,
33
+ verb = 'get',
34
+ body = '',
35
+ params = '',
36
+ services = true)
37
+ svc_str = services ? '/services' : ''
38
+ request_url =
39
+ "https://#{@host_name}/#{@subscription_id}#{svc_str}/#{service_name}"
33
40
  print '.'
41
+ response = http_query(request_url, verb, body, params)
42
+ if response.code.to_i == 307
43
+ Chef::Log.debug "Redirect to #{response['Location']}"
44
+ response = http_query(response['Location'], verb, body, params)
45
+ end
46
+ @last_request_id = response['x-ms-request-id']
47
+ response
48
+ end
49
+
50
+ def http_query(request_url, verb, body, params)
34
51
  uri = URI.parse(request_url)
35
52
  uri.query = params
36
53
  http = http_setup(uri)
@@ -39,13 +56,17 @@ module AzureAPI
39
56
  @last_request_id = response['x-ms-request-id']
40
57
  response
41
58
  end
59
+
42
60
  def query_for_completion()
43
61
  request_url = "https://#{@host_name}/#{@subscription_id}/operations/#{@last_request_id}"
44
- uri = URI.parse(request_url)
45
- http = http_setup(uri)
46
- request = request_setup(uri, 'get', '')
47
- response = http.request(request)
62
+ response = http_query(request_url, 'get', '', '')
63
+ if response.code.to_i == 307
64
+ Chef::Log.debug "Redirect to #{response['Location']}"
65
+ response = http_query(response['Location'], 'get', '', '')
66
+ end
67
+ response
48
68
  end
69
+
49
70
  def http_setup(uri)
50
71
  http = Net::HTTP.new(uri.host, uri.port)
51
72
  store = OpenSSL::X509::Store.new
@@ -72,9 +93,12 @@ module AzureAPI
72
93
  request = Net::HTTP::Post.new(uri.request_uri)
73
94
  elsif verb == 'delete'
74
95
  request = Net::HTTP::Delete.new(uri.request_uri)
96
+ elsif verb == 'put'
97
+ request = Net::HTTP::Put.new(uri.request_uri)
75
98
  end
76
- request["x-ms-version"] = "2013-03-01"
77
- request["content-type"] = "application/xml"
99
+ text = verb == 'put'
100
+ request["x-ms-version"] = "2013-08-01"
101
+ request["content-type"] = text ? "text/plain" : "application/xml"
78
102
  request["accept"] = "application/xml"
79
103
  request["accept-charset"] = "utf-8"
80
104
  request.body = body
data/lib/azure/role.rb CHANGED
@@ -77,74 +77,95 @@ class Azure
77
77
  def delete(name, params)
78
78
  role = find(name)
79
79
  if role != nil
80
- if alone_on_hostedservice(role)
81
- servicecall = "hostedservices/#{role.hostedservicename}/deployments" +
82
- "/#{role.deployname}"
83
- else
84
- servicecall = "hostedservices/#{role.hostedservicename}/deployments" +
85
- "/#{role.deployname}/roles/#{role.name}"
86
- end
87
-
88
80
  roleXML = nil
81
+ roleXML = @connection.query_azure("hostedservices/#{role.hostedservicename}", "get", "", "embed-detail=true")
82
+ osdisk = roleXML.css(roleXML, 'OSVirtualHardDisk')
83
+ disk_name = xml_content(osdisk, 'DiskName')
84
+ storage_account_name = xml_content(osdisk, 'MediaLink').gsub("http://", "").gsub(/.blob(.*)$/, "")
89
85
 
90
- unless params[:preserve_azure_os_disk]
91
- roleXML = @connection.query_azure(servicecall, "get")
86
+ if !params[:preserve_azure_os_disk] && !params[:preserve_azure_vhd] && !params[:wait]
87
+ # default compmedia = true. So, it deletes role and associated resources
88
+ check_and_delete_role_and_resources(params, role)
89
+ else
90
+ # compmedia = false. So, it deletes only role and not associated resources
91
+ check_and_delete_role_and_resources(params, role, compmedia=false)
92
+ check_and_delete_disks(params, disk_name)
93
+ check_and_delete_service(params)
92
94
  end
95
+ check_and_delete_storage(params, disk_name, storage_account_name)
96
+ end
97
+ end
93
98
 
94
- @connection.query_azure(servicecall, "delete")
95
- # delete role from local cache as well.
96
- @connection.hosts.find(role.hostedservicename).delete_role(role)
97
- @roles.delete(role) if @roles
98
-
99
- unless params[:preserve_azure_dns_name]
100
- unless params[:azure_dns_name].nil?
101
- roles_using_same_service = find_roles_within_hostedservice(params[:azure_dns_name])
102
- if roles_using_same_service.size <= 1
103
- servicecall = "hostedservices/" + params[:azure_dns_name]
104
- @connection.query_azure(servicecall, "delete")
105
- end
106
- end
99
+ def check_and_delete_role_and_resources(params, role, compmedia=true)
100
+ if alone_on_hostedservice(role)
101
+ if !params[:preserve_azure_dns_name] && compmedia
102
+ servicecall = "hostedservices/#{role.hostedservicename}"
103
+ else
104
+ servicecall = "hostedservices/#{role.hostedservicename}/deployments/#{role.deployname}"
107
105
  end
106
+ else
107
+ servicecall = "hostedservices/#{role.hostedservicename}/deployments" +
108
+ "/#{role.deployname}/roles/#{role.name}"
109
+ end
110
+ if compmedia
111
+ @connection.query_azure(servicecall, "delete", "", "comp=media", wait=params[:wait])
112
+ else
113
+ @connection.query_azure(servicecall, "delete")
114
+ end
108
115
 
109
- unless params[:preserve_azure_os_disk]
110
- osdisk = roleXML.css(roleXML, 'OSVirtualHardDisk')
111
- disk_name = xml_content(osdisk, 'DiskName')
112
- servicecall = "disks/#{disk_name}"
113
- storage_account = @connection.query_azure(servicecall, "get")
114
-
115
- # OS Disk can only be deleted if it is detached from the VM.
116
- # So Iteratively check for disk detachment from the VM while waiting for 5 minutes ,
117
- # exit otherwise after 12 attempts.
118
- for attempt in 0..12
119
- break if @connection.query_azure(servicecall, "get").search("AttachedTo").text == ""
120
- if attempt == 12 then puts "The associated disk could not be deleted due to time out." else sleep 25 end
121
- end
122
-
123
- unless params[:preserve_azure_vhd]
124
- @connection.query_azure(servicecall, 'delete', '', 'comp=media')
125
- else
126
- @connection.query_azure(servicecall, 'delete')
127
- end
128
-
129
- if params[:delete_azure_storage_account]
130
- storage_account_name = xml_content(storage_account, "MediaLink")
131
- storage_account_name = storage_account_name.gsub("http://", "").gsub(/.blob(.*)$/, "")
116
+ # delete role from local cache as well.
117
+ @connection.hosts.find(role.hostedservicename).delete_role(role)
118
+ @roles.delete(role) if @roles
119
+ end
132
120
 
133
- begin
134
- @connection.query_azure("storageservices/#{storage_account_name}", "delete")
135
- rescue Exception => ex
136
- ui.warn("#{ex.message}")
137
- ui.warn("#{ex.backtrace.join("\n")}")
138
- end
121
+ def check_and_delete_disks(params, disk_name)
122
+ servicecall = "disks/#{disk_name}"
123
+ unless params[:preserve_azure_os_disk]
124
+ # OS Disk can only be deleted if it is detached from the VM.
125
+ # So Iteratively check for disk detachment from the VM while waiting for 5 minutes ,
126
+ # exit otherwise after 12 attempts.
127
+ for attempt in 0..12
128
+ break if @connection.query_azure(servicecall, "get").search("AttachedTo").text == ""
129
+ if attempt == 12 then puts "The associated disk could not be deleted due to time out." else sleep 25 end
130
+ end
131
+ unless params[:preserve_azure_vhd]
132
+ @connection.query_azure(servicecall, 'delete', '', 'comp=media', wait=params[:wait])
133
+ else
134
+ @connection.query_azure(servicecall, 'delete')
135
+ end
136
+ end
137
+ end
139
138
 
139
+ def check_and_delete_service(params)
140
+ unless params[:preserve_azure_dns_name]
141
+ unless params[:azure_dns_name].nil?
142
+ roles_using_same_service = find_roles_within_hostedservice(params[:azure_dns_name])
143
+ if roles_using_same_service.size <= 1
144
+ servicecall = "hostedservices/" + params[:azure_dns_name]
145
+ @connection.query_azure(servicecall, "delete")
140
146
  end
141
-
142
147
  end
148
+ end
149
+ end
143
150
 
144
-
151
+ def check_and_delete_storage(params, disk_name, storage_account_name)
152
+ if params[:delete_azure_storage_account]
153
+ # Iteratively check for disk deletion
154
+ for attempt in 0..12
155
+ break unless @connection.query_azure("disks").search("Name").text.include?(disk_name)
156
+ if attempt == 12 then puts "The associated disk could not be deleted due to time out." else sleep 25 end
157
+ end
158
+ begin
159
+ @connection.query_azure("storageservices/#{storage_account_name}", "delete")
160
+ rescue Exception => ex
161
+ ui.warn("#{ex.message}")
162
+ ui.warn("#{ex.backtrace.join("\n")}")
163
+ end
145
164
  end
146
165
  end
147
166
 
167
+ private :check_and_delete_role_and_resources, :check_and_delete_disks, :check_and_delete_service, :check_and_delete_storage
168
+
148
169
  end
149
170
 
150
171
  class Role
@@ -210,7 +231,7 @@ class Azure
210
231
  xml.SSH {
211
232
  xml.PublicKeys {
212
233
  xml.PublicKey {
213
- xml.Fingerprint params[:fingerprint]
234
+ xml.Fingerprint params[:fingerprint].to_s.upcase
214
235
  xml.Path '/home/' + params[:ssh_user] + '/.ssh/authorized_keys'
215
236
  }
216
237
  }
@@ -228,6 +249,15 @@ class Azure
228
249
  xml.ResetPasswordOnFirstLogon 'false'
229
250
  xml.EnableAutomaticUpdates 'false'
230
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'
257
+ }
258
+ }
259
+ }
260
+ end
231
261
  }
232
262
  end
233
263