knife-azure 1.0.2 → 1.1.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.
data/lib/azure/host.rb CHANGED
@@ -21,30 +21,69 @@ class Azure
21
21
  def initialize(connection)
22
22
  @connection=connection
23
23
  end
24
+
25
+ # force_load should be true when there is something in local cache and we want to reload
26
+ # first call is always load.
27
+ def load(force_load = false)
28
+ if not @hosted_services || force_load
29
+ @hosted_services = begin
30
+ hosted_services = Hash.new
31
+ responseXML = @connection.query_azure('hostedservices')
32
+ servicesXML = responseXML.css('HostedServices HostedService')
33
+ servicesXML.each do |serviceXML|
34
+ host = Host.new(@connection).parse(serviceXML)
35
+ hosted_services[host.name] = host
36
+ end
37
+ hosted_services
38
+ end
39
+ end
40
+ @hosted_services
41
+ end
42
+
24
43
  def all
25
- hosted_services = Array.new
26
- responseXML = @connection.query_azure('hostedservices')
27
- servicesXML = responseXML.css('HostedServices HostedService')
28
- servicesXML.each do |serviceXML|
29
- host = Host.new(@connection)
30
- hosted_services << host.parse(serviceXML)
44
+ self.load.values
45
+ end
46
+
47
+ # first look up local cache if we have already loaded list.
48
+ def exists?(name)
49
+ return @hosted_services.key?(name) if @hosted_services
50
+ self.exists_on_cloud?(name)
51
+ end
52
+
53
+ # Look up on cloud and not local cache
54
+ def exists_on_cloud?(name)
55
+ 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
58
+ false
59
+ else
60
+ true
31
61
  end
32
- hosted_services
33
62
  end
34
- def exists(name)
35
- hostExists = false
36
- self.all.each do |host|
37
- next unless host.name == name
38
- hostExists = true
63
+
64
+ # first look up local cache if we have already loaded list.
65
+ def find(name)
66
+ return @hosted_services[name] if @hosted_services && @hosted_services.key?(name)
67
+ self.fetch_from_cloud(name)
68
+ end
69
+
70
+ # Look up hosted service on cloud and not local cache
71
+ def fetch_from_cloud(name)
72
+ 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
+ nil
76
+ else
77
+ Host.new(@connection).parse(ret_val)
39
78
  end
40
- hostExists
41
79
  end
80
+
42
81
  def create(params)
43
82
  host = Host.new(@connection)
44
83
  host.create(params)
45
84
  end
46
85
  def delete(name)
47
- if self.exists name
86
+ if self.exists?(name)
48
87
  servicecall = "hostedservices/" + name
49
88
  @connection.query_azure(servicecall, "delete")
50
89
  end
@@ -58,8 +97,11 @@ class Azure
58
97
  attr_accessor :connection, :name, :url, :label
59
98
  attr_accessor :dateCreated, :description, :location
60
99
  attr_accessor :dateModified, :status
100
+
61
101
  def initialize(connection)
62
102
  @connection = connection
103
+ @deploys_loaded = false
104
+ @deploys = Hash.new
63
105
  end
64
106
  def parse(serviceXML)
65
107
  @name = xml_content(serviceXML, 'ServiceName')
@@ -75,10 +117,10 @@ class Azure
75
117
  def create(params)
76
118
  builder = Nokogiri::XML::Builder.new do |xml|
77
119
  xml.CreateHostedService('xmlns'=>'http://schemas.microsoft.com/windowsazure') {
78
- xml.ServiceName params[:hosted_service_name]
79
- xml.Label Base64.encode64(params[:hosted_service_name])
80
- xml.Description params[:hosted_service_description] || 'Explicitly created hosted service'
81
- xml.Location params[:service_location] || 'West US'
120
+ xml.ServiceName params[:azure_dns_name]
121
+ xml.Label Base64.encode64(params[:azure_dns_name])
122
+ xml.Description 'Explicitly created hosted service'
123
+ xml.Location params[:azure_service_location] || 'West US'
82
124
  }
83
125
  end
84
126
  @connection.query_azure("hostedservices", "post", builder.to_xml)
@@ -86,5 +128,42 @@ class Azure
86
128
  def details
87
129
  response = @connection.query_azure('hostedservices/' + @name + '?embed-detail=true')
88
130
  end
131
+
132
+ # Deployments within this hostedservice
133
+ def add_deploy(deploy)
134
+ @deploys[deploy.name] = deploy
135
+ end
136
+
137
+ def delete_role(role)
138
+ deploys.each { |d| d.delete_role_if_present(role) }
139
+ end
140
+
141
+ def deploys
142
+ # check if we have deploys loaded, else load.
143
+ if (@deploys.length == 0) && !@deploys_loaded
144
+ deploy = Deploy.new(@connection)
145
+ deploy.retrieve(@name)
146
+ @deploys[deploy.name] = deploy
147
+ @deploys_loaded = true
148
+ end
149
+ @deploys.values
150
+ end
151
+
152
+ def roles
153
+ roles = []
154
+ deploys.each do |deploy|
155
+ roles.concat(deploy.roles) if deploy.roles
156
+ end
157
+ roles
158
+ end
159
+
160
+ def find_role(role_name, deploy_name = nil)
161
+ return @deploys[deploy_name].find_role(role_name) if deploy_name && deploys
162
+ # else lookup all deploys within hostedservice
163
+ deploys.each do |deploy|
164
+ role = deploy.find_role(role_name)
165
+ return role if role
166
+ end
167
+ end
89
168
  end
90
169
  end
data/lib/azure/image.rb CHANGED
@@ -21,23 +21,29 @@ class Azure
21
21
  def initialize(connection)
22
22
  @connection=connection
23
23
  end
24
- def all
25
- images = Array.new
26
- response = @connection.query_azure('images')
27
- osimages = response.css('OSImage')
28
- osimages.each do |image|
29
- item = Image.new(image)
30
- images << item
24
+ def load
25
+ @images ||= begin
26
+ images = Hash.new
27
+ response = @connection.query_azure('images')
28
+ osimages = response.css('OSImage')
29
+ osimages.each do |image|
30
+ item = Image.new(image)
31
+ images[item.name] = item
32
+ end
33
+ images
31
34
  end
32
- images
33
35
  end
34
- def exists(name)
35
- imageExists = false
36
- self.all.each do |host|
37
- next unless host.name == name
38
- imageExists = true
39
- end
40
- imageExists
36
+
37
+ def all
38
+ self.load.values
39
+ end
40
+
41
+ def exists?(name)
42
+ self.all.key?(name)
43
+ end
44
+
45
+ def find(name)
46
+ self.load[name]
41
47
  end
42
48
  end
43
49
  end
data/lib/azure/rest.rb CHANGED
@@ -24,23 +24,10 @@ module AzureAPI
24
24
  class Rest
25
25
  def initialize(params)
26
26
  @subscription_id = params[:azure_subscription_id]
27
- @pem_file = File.read find_pem(params[:azure_mgmt_cert])
28
- @host_name = params[:azure_host_name]
27
+ @pem_file = params[:azure_mgmt_cert]
28
+ @host_name = params[:azure_api_host_name]
29
29
  @verify_ssl = params[:verify_ssl_cert]
30
30
  end
31
- def find_pem(name)
32
- config_dir = Chef::Knife.chef_config_dir
33
- if File.exist? name
34
- pem_file = name
35
- elsif config_dir && File.exist?(File.join(config_dir, name))
36
- pem_file = File.join(config_dir, name)
37
- elsif File.exist?(File.join(ENV['HOME'], '.chef', name))
38
- pem_file = File.join(ENV['HOME'], '.chef', name)
39
- else
40
- raise 'Unable to find certificate pem file - ' + name
41
- end
42
- pem_file
43
- end
44
31
  def query_azure(service_name, verb = 'get', body = '')
45
32
  request_url = "https://#{@host_name}/#{@subscription_id}/services/#{service_name}"
46
33
  print '.'
@@ -69,8 +56,12 @@ module AzureAPI
69
56
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE
70
57
  end
71
58
  http.use_ssl = true
72
- http.cert = OpenSSL::X509::Certificate.new(@pem_file)
73
- http.key = OpenSSL::PKey::RSA.new(@pem_file)
59
+ begin
60
+ http.cert = OpenSSL::X509::Certificate.new(@pem_file)
61
+ rescue OpenSSL::X509::CertificateError => err
62
+ raise "Invalid Azure Certificate pem file. Error: #{err}"
63
+ end
64
+ http.key = OpenSSL::PKey::RSA.new(@pem_file)
74
65
  http
75
66
  end
76
67
  def request_setup(uri, verb, body)
@@ -95,7 +86,7 @@ module AzureAPI
95
86
  puts response.code
96
87
  puts "=== response.inspect ==="
97
88
  puts response.inspect
98
- puts "=== all of the headers ==="
89
+ puts "=== all of the headers ==="
99
90
  puts response.each_header { |h, j| puts h.inspect + ' : ' + j.inspect}
100
91
  end
101
92
  end
data/lib/azure/role.rb CHANGED
@@ -15,14 +15,16 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
  #
18
-
18
+ require 'securerandom'
19
19
  class Azure
20
20
  class Roles
21
+ include AzureUtility
21
22
  attr_accessor :connection, :roles
22
23
  def initialize(connection)
23
24
  @connection = connection
24
25
  @roles = nil
25
26
  end
27
+ # do not use this unless you want a list of all roles(vms) in your subscription
26
28
  def all
27
29
  @roles = Array.new
28
30
  @connection.deploys.all.each do |deploy|
@@ -32,51 +34,121 @@ class Azure
32
34
  end
33
35
  @roles
34
36
  end
35
- def find(name)
36
- if @roles == nil
37
- all
37
+
38
+ def find_roles_within_hostedservice(hostedservicename)
39
+ host = @connection.hosts.find(hostedservicename)
40
+ (host) ? host.roles : nil # nil says invalid hosted service
41
+ end
42
+
43
+ def find_in_hosted_service(role_name, hostedservicename)
44
+ host = @connection.hosts.find(hostedservicename)
45
+ return nil if host.nil?
46
+ host.find_role(role_name)
47
+ end
48
+
49
+ def find(role_name, params= nil)
50
+ if params && params[:azure_dns_name]
51
+ return find_in_hosted_service(role_name, params[:azure_dns_name])
38
52
  end
53
+
54
+ all if @roles == nil
55
+
56
+ # TODO - optimize this lookup
39
57
  @roles.each do |role|
40
- if(role.name == name)
41
- return role
58
+ if(role.name == role_name)
59
+ return role
42
60
  end
43
61
  end
44
- nil
62
+ nil
45
63
  end
46
- def alone_on_host(name)
47
- found_role = find(name)
48
- @roles.each do |role|
49
- if (role.name != found_role.name &&
50
- role.deployname == found_role.deployname &&
51
- role.hostedservicename == found_role.hostedservicename)
52
- return false;
53
- end
64
+
65
+ def alone_on_hostedservice(found_role)
66
+ roles = find_roles_within_hostedservice(found_role.hostedservicename)
67
+ if roles && roles.length > 1
68
+ return false
54
69
  end
55
- true
70
+ return true
56
71
  end
57
- def exists(name)
72
+
73
+ def exists?(name)
58
74
  find(name) != nil
59
75
  end
60
- def delete(name)
76
+
77
+ def delete(name, params)
61
78
  role = find(name)
62
79
  if role != nil
63
- if alone_on_host(name)
80
+ if alone_on_hostedservice(role)
64
81
  servicecall = "hostedservices/#{role.hostedservicename}/deployments" +
65
82
  "/#{role.deployname}"
66
83
  else
67
84
  servicecall = "hostedservices/#{role.hostedservicename}/deployments" +
68
85
  "/#{role.deployname}/roles/#{role.name}"
69
86
  end
70
- @connection.query_azure(servicecall, "delete")
87
+
88
+ roleXML = nil
89
+
90
+ unless params[:preserve_azure_os_disk]
91
+ roleXML = @connection.query_azure(servicecall, "get")
92
+ end
93
+
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
107
+ end
108
+
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
+ @connection.query_azure(servicecall, "delete")
123
+
124
+ if params[:delete_azure_storage_account]
125
+ storage_account_name = xml_content(storage_account, "MediaLink")
126
+ storage_account_name = storage_account_name.gsub("http://", "").gsub(/.blob(.*)$/, "")
127
+
128
+ begin
129
+ @connection.query_azure("storageservices/#{storage_account_name}", "delete")
130
+ rescue Exception => ex
131
+ ui.warn("#{ex.message}")
132
+ ui.warn("#{ex.backtrace.join("\n")}")
133
+ end
134
+
135
+ end
136
+
137
+ end
138
+
139
+
71
140
  end
72
- #@connection.disks.clear_unattached
73
141
  end
142
+
74
143
  end
144
+
75
145
  class Role
76
146
  include AzureUtility
77
- attr_accessor :connection, :name, :status, :size, :ipaddress
78
- attr_accessor :sshport, :sshipaddress, :hostedservicename, :deployname
147
+ attr_accessor :connection, :name, :status, :size, :ipaddress, :publicipaddress
148
+ attr_accessor :sshport, :hostedservicename, :deployname
149
+ attr_accessor :winrmport
79
150
  attr_accessor :hostname, :tcpports, :udpports
151
+
80
152
  def initialize(connection)
81
153
  @connection = connection
82
154
  end
@@ -90,11 +162,14 @@ class Azure
90
162
  @deployname = deployname
91
163
  @tcpports = Array.new
92
164
  @udpports = Array.new
165
+
93
166
  endpoints = roleXML.css('InstanceEndpoint')
167
+ @publicipaddress = xml_content(endpoints[0], 'Vip') if !endpoints.empty?
94
168
  endpoints.each do |endpoint|
95
169
  if xml_content(endpoint, 'Name').downcase == 'ssh'
96
170
  @sshport = xml_content(endpoint, 'PublicPort')
97
- @sshipaddress = xml_content(endpoint, 'Vip')
171
+ elsif xml_content(endpoint, 'Name').downcase == 'winrm'
172
+ @winrmport = xml_content(endpoint, 'PublicPort')
98
173
  else
99
174
  hash = Hash.new
100
175
  hash['Name'] = xml_content(endpoint, 'Name')
@@ -115,34 +190,72 @@ class Azure
115
190
  'xmlns'=>'http://schemas.microsoft.com/windowsazure',
116
191
  'xmlns:i'=>'http://www.w3.org/2001/XMLSchema-instance'
117
192
  ) {
118
- xml.RoleName {xml.text params[:role_name]}
193
+ xml.RoleName {xml.text params[:azure_vm_name]}
119
194
  xml.OsVersion('i:nil' => 'true')
120
195
  xml.RoleType 'PersistentVMRole'
121
196
  xml.ConfigurationSets {
122
- xml.ConfigurationSet('i:type' => 'LinuxProvisioningConfigurationSet') {
123
- xml.ConfigurationSetType 'LinuxProvisioningConfiguration'
124
- xml.HostName params[:host_name]
125
- xml.UserName params[:ssh_user]
126
- xml.UserPassword params[:ssh_password]
127
- xml.DisableSshPasswordAuthentication 'false'
128
- }
197
+ if params[:os_type] == 'Linux'
198
+
199
+ xml.ConfigurationSet('i:type' => 'LinuxProvisioningConfigurationSet') {
200
+ xml.ConfigurationSetType 'LinuxProvisioningConfiguration'
201
+ xml.HostName params[:azure_vm_name]
202
+ xml.UserName params[:ssh_user]
203
+ unless params[:identity_file].nil?
204
+ xml.DisableSshPasswordAuthentication 'true'
205
+ xml.SSH {
206
+ xml.PublicKeys {
207
+ xml.PublicKey {
208
+ xml.Fingerprint params[:fingerprint]
209
+ xml.Path '/home/' + params[:ssh_user] + '/.ssh/authorized_keys'
210
+ }
211
+ }
212
+ }
213
+ else
214
+ xml.UserPassword params[:ssh_password]
215
+ xml.DisableSshPasswordAuthentication 'false'
216
+ end
217
+ }
218
+ elsif params[:os_type] == 'Windows'
219
+ xml.ConfigurationSet('i:type' => 'WindowsProvisioningConfigurationSet') {
220
+ xml.ConfigurationSetType 'WindowsProvisioningConfiguration'
221
+ xml.ComputerName params[:azure_vm_name]
222
+ xml.AdminPassword params[:admin_password]
223
+ xml.ResetPasswordOnFirstLogon 'false'
224
+ xml.EnableAutomaticUpdates 'false'
225
+
226
+ }
227
+ end
228
+
129
229
  xml.ConfigurationSet('i:type' => 'NetworkConfigurationSet') {
130
230
  xml.ConfigurationSetType 'NetworkConfiguration'
131
231
  xml.InputEndpoints {
132
- xml.InputEndpoint {
133
- xml.LocalPort '22'
134
- xml.Name 'SSH'
135
- xml.Protocol 'TCP'
136
- }
232
+ if params[:bootstrap_proto].downcase == 'ssh'
233
+ xml.InputEndpoint {
234
+ xml.LocalPort '22'
235
+ xml.Name 'SSH'
236
+ xml.Port params[:port]
237
+ xml.Protocol 'TCP'
238
+ }
239
+ elsif params[:bootstrap_proto].downcase == 'winrm' and params[:os_type] == 'Windows'
240
+ xml.InputEndpoint {
241
+ xml.LocalPort '5985'
242
+ xml.Name 'WinRM'
243
+ xml.Port params[:port]
244
+ xml.Protocol 'TCP'
245
+ }
246
+ end
247
+
137
248
  if params[:tcp_endpoints]
138
249
  params[:tcp_endpoints].split(',').each do |endpoint|
139
250
  ports = endpoint.split(':')
140
251
  xml.InputEndpoint {
141
252
  xml.LocalPort ports[0]
142
- xml.Name 'tcpport_' + ports[0] + '_' + params[:host_name]
253
+ xml.Name 'tcpport_' + ports[0] + '_' + params[:azure_vm_name]
143
254
  if ports.length > 1
144
255
  xml.Port ports[1]
145
- end
256
+ else
257
+ xml.Port ports[0]
258
+ end
146
259
  xml.Protocol 'TCP'
147
260
  }
148
261
  end
@@ -152,10 +265,12 @@ class Azure
152
265
  ports = endpoint.split(':')
153
266
  xml.InputEndpoint {
154
267
  xml.LocalPort ports[0]
155
- xml.Name 'udpport_' + ports[0] + '_' + params[:host_name]
268
+ xml.Name 'udpport_' + ports[0] + '_' + params[:azure_vm_name]
156
269
  if ports.length > 1
157
270
  xml.Port ports[1]
158
- end
271
+ else
272
+ xml.Port ports[0]
273
+ end
159
274
  xml.Protocol 'UDP'
160
275
  }
161
276
  end
@@ -163,20 +278,22 @@ class Azure
163
278
  }
164
279
  }
165
280
  }
166
- xml.Label Base64.encode64(params[:role_name]).strip
281
+ xml.Label Base64.encode64(params[:azure_vm_name]).strip
167
282
  xml.OSVirtualHardDisk {
168
- xml.MediaLink 'http://' + params[:storage_account] + '.blob.core.windows.net/vhds/' + (params[:os_disk_name] || Time.now.strftime('disk_%Y_%m_%d_%H_%M')) + '.vhd'
169
- xml.SourceImageName params[:source_image]
283
+ disk_name = params[:azure_os_disk_name] || "disk_" + SecureRandom.uuid
284
+ xml.DiskName disk_name
285
+ xml.MediaLink 'http://' + params[:azure_storage_account] + '.blob.core.windows.net/vhds/' + disk_name + '.vhd'
286
+ xml.SourceImageName params[:azure_source_image]
170
287
  }
171
- xml.RoleSize params[:role_size]
288
+ xml.RoleSize params[:azure_vm_size]
172
289
  }
173
- end
290
+ end
174
291
  builder.doc
175
292
  end
176
293
  def create(params, roleXML)
177
- servicecall = "hostedservices/#{params[:hosted_service_name]}/deployments" +
294
+ servicecall = "hostedservices/#{params[:azure_dns_name]}/deployments" +
178
295
  "/#{params['deploy_name']}/roles"
179
- @connection.query_azure(servicecall, "post", roleXML.to_xml)
296
+ @connection.query_azure(servicecall, "post", roleXML.to_xml)
180
297
  end
181
298
  end
182
299
  end