knife-azure 1.0.2 → 1.1.0

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