knife-azure 3.0.6 → 4.0.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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/lib/azure/custom_errors.rb +1 -1
  3. data/lib/azure/resource_management/ARM_deployment_template.rb +5 -5
  4. data/lib/azure/resource_management/ARM_interface.rb +4 -6
  5. data/lib/azure/resource_management/windows_credentials.rb +2 -2
  6. data/lib/chef/knife/azurerm_server_create.rb +1 -1
  7. data/lib/chef/knife/bootstrap/bootstrapper.rb +5 -10
  8. data/lib/chef/knife/helpers/azurerm_base.rb +4 -4
  9. data/lib/knife-azure/version.rb +1 -1
  10. metadata +30 -43
  11. data/lib/azure/service_management/ASM_interface.rb +0 -310
  12. data/lib/azure/service_management/ag.rb +0 -99
  13. data/lib/azure/service_management/certificate.rb +0 -235
  14. data/lib/azure/service_management/connection.rb +0 -102
  15. data/lib/azure/service_management/deploy.rb +0 -221
  16. data/lib/azure/service_management/disk.rb +0 -68
  17. data/lib/azure/service_management/host.rb +0 -184
  18. data/lib/azure/service_management/image.rb +0 -94
  19. data/lib/azure/service_management/loadbalancer.rb +0 -78
  20. data/lib/azure/service_management/rest.rb +0 -126
  21. data/lib/azure/service_management/role.rb +0 -717
  22. data/lib/azure/service_management/storageaccount.rb +0 -127
  23. data/lib/azure/service_management/utility.rb +0 -40
  24. data/lib/azure/service_management/vnet.rb +0 -134
  25. data/lib/chef/knife/azure_ag_create.rb +0 -73
  26. data/lib/chef/knife/azure_ag_list.rb +0 -35
  27. data/lib/chef/knife/azure_image_list.rb +0 -56
  28. data/lib/chef/knife/azure_internal-lb_create.rb +0 -74
  29. data/lib/chef/knife/azure_internal-lb_list.rb +0 -35
  30. data/lib/chef/knife/azure_server_create.rb +0 -531
  31. data/lib/chef/knife/azure_server_delete.rb +0 -136
  32. data/lib/chef/knife/azure_server_list.rb +0 -38
  33. data/lib/chef/knife/azure_server_show.rb +0 -41
  34. data/lib/chef/knife/azure_vnet_create.rb +0 -74
  35. data/lib/chef/knife/azure_vnet_list.rb +0 -35
  36. data/lib/chef/knife/bootstrap_azure.rb +0 -191
  37. data/lib/chef/knife/helpers/azure_base.rb +0 -392
@@ -1,392 +0,0 @@
1
- # Author:: Barry Davis (barryd@jetstreamsoftware.com)
2
- # Author:: Seth Chisamore (<schisamo@chef.io>)
3
- # Copyright:: Copyright (c) Chef Software 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 "chef/knife"
20
-
21
- class Chef
22
- class Knife
23
- module AzureBase
24
- # :nodoc:
25
- # Would prefer to do this in a rational way, but can't be done b/c of
26
- # Mixlib::CLI's design :(
27
- def self.included(includer)
28
- includer.class_eval do
29
- deps do
30
- require "readline"
31
- require "chef/json_compat"
32
- require_relative "../../../azure/service_management/ASM_interface"
33
- end
34
-
35
- option :azure_subscription_id,
36
- short: "-S ID",
37
- long: "--azure-subscription-id ID",
38
- description: "Your Azure subscription ID"
39
-
40
- option :azure_mgmt_cert,
41
- short: "-p FILENAME",
42
- long: "--azure-mgmt-cert FILENAME",
43
- description: "Your Azure PEM file name"
44
-
45
- option :azure_api_host_name,
46
- short: "-H HOSTNAME",
47
- long: "--azure-api-host-name HOSTNAME",
48
- description: "Your Azure host name"
49
-
50
- option :verify_ssl_cert,
51
- long: "--verify-ssl-cert",
52
- description: "Verify SSL Certificates for communication over HTTPS",
53
- boolean: true,
54
- default: false
55
-
56
- option :azure_publish_settings_file,
57
- long: "--azure-publish-settings-file FILENAME",
58
- description: "Your Azure Publish Settings File"
59
- end
60
- end
61
-
62
- def is_image_windows?
63
- images = service.list_images
64
- target_image = images.select { |i| i.name == config[:azure_source_image] }
65
- if target_image[0].nil?
66
- ui.error('Invalid image. Use the command "knife azure image list" to verify the image name')
67
- exit 1
68
- else
69
- target_image[0].os == "Windows"
70
- end
71
- end
72
-
73
- def service
74
- @service ||= begin
75
- service = Azure::ServiceManagement::ASMInterface.new(
76
- azure_subscription_id: config[:azure_subscription_id],
77
- azure_mgmt_cert: config[:azure_mgmt_cert],
78
- azure_api_host_name: config[:azure_api_host_name],
79
- verify_ssl_cert: config[:verify_ssl_cert]
80
- )
81
- end
82
- @service.ui = ui
83
- @service
84
- end
85
-
86
- def msg_pair(label, value, color = :cyan)
87
- if value && !value.to_s.empty?
88
- puts "#{ui.color(label, color)}: #{value}"
89
- end
90
- end
91
-
92
- def msg_server_summary(server)
93
- puts "\n"
94
- msg_pair("DNS Name", server.hostedservicename + ".cloudapp.net")
95
- msg_pair("VM Name", server.name)
96
- msg_pair("Size", server.size)
97
- msg_pair("Azure Source Image", config[:azure_source_image])
98
- msg_pair("Azure Service Location", config[:azure_service_location])
99
- msg_pair("Public Ip Address", server.publicipaddress)
100
- msg_pair("Private Ip Address", server.ipaddress)
101
- msg_pair("SSH Port", server.sshport) unless server.sshport.nil?
102
- msg_pair("WinRM Port", server.winrmport) unless server.winrmport.nil?
103
- msg_pair("TCP Ports", server.tcpports) unless server.tcpports.nil? || server.tcpports.empty?
104
- msg_pair("UDP Ports", server.udpports) unless server.udpports.nil? || server.udpports.empty?
105
- msg_pair("Environment", config[:environment] || "_default")
106
- msg_pair("Runlist", config[:run_list]) unless config[:run_list].empty?
107
- puts "\n"
108
- end
109
-
110
- def pretty_key(key)
111
- key.to_s.tr("_", " ").gsub(/\w+/) { |w| w =~ /(ssh)|(aws)/i ? w.upcase : w.capitalize }
112
- end
113
-
114
- # validate command pre-requisites (cli options)
115
- # (config[:connection_password].length <= 6 && config[:connection_password].length >= 72)
116
- def validate_params!
117
- if config[:connection_password] && !config[:connection_password].length.between?(6, 72)
118
- ui.error("The supplied connection password must be 6-72 characters long and meet password complexity requirements")
119
- exit 1
120
- end
121
-
122
- if config[:azure_connect_to_existing_dns] && config[:azure_vm_name].nil?
123
- ui.error("Specify the VM name using --azure-vm-name option, since you are connecting to existing dns")
124
- exit 1
125
- end
126
-
127
- unless !!config[:azure_service_location] ^ !!config[:azure_affinity_group]
128
- ui.error("Specify either --azure-service-location or --azure-affinity-group")
129
- exit 1
130
- end
131
-
132
- unless service.valid_image?(config[:azure_source_image])
133
- ui.error("Image '#{config[:azure_source_image]}' is invalid")
134
- exit 1
135
- end
136
-
137
- # Validate join domain requirements.
138
- if config[:azure_domain_name] || config[:azure_domain_user]
139
- if config[:azure_domain_user].nil? || config[:azure_domain_passwd].nil?
140
- ui.error("Must specify both --azure-domain-user and --azure-domain-passwd.")
141
- exit 1
142
- end
143
- end
144
-
145
- if config[:winrm_ssl] && config[:thumbprint].nil? && config[:winrm_no_verify_cert].nil?
146
- ui.error("The SSL transport was specified without the --thumbprint option. Specify a thumbprint, or alternatively set the --winrm-no-verify-cert option to skip verification.")
147
- exit 1
148
- end
149
-
150
- if config[:extended_logs] && config[:connection_protocol] != "cloud-api"
151
- ui.error("--extended-logs option only works with --bootstrap-protocol cloud-api")
152
- exit 1
153
- end
154
-
155
- if config[:connection_protocol] == "cloud-api" && config[:azure_vm_name].nil? && config[:azure_dns_name].nil?
156
- ui.error("Specifying the DNS name using --azure-dns-name or VM name using --azure-vm-name option is required with --bootstrap-protocol cloud-api")
157
- exit 1
158
- end
159
-
160
- if config[:daemon]
161
- unless is_image_windows?
162
- raise ArgumentError, "The daemon option is only supported for Windows nodes."
163
- end
164
-
165
- unless config[:connection_protocol] == "cloud-api"
166
- raise ArgumentError, "The --daemon option requires the use of --bootstrap-protocol cloud-api"
167
- end
168
-
169
- unless %w{none service task}.include?(config[:daemon].downcase)
170
- raise ArgumentError, "Invalid value for --daemon option. Valid values are 'none', 'service' and 'task'."
171
- end
172
- end
173
- end
174
-
175
- # validates keys
176
- def validate!(keys)
177
- errors = []
178
- keys.each do |k|
179
- if config[k].nil?
180
- errors << "You did not provide a valid '#{pretty_key(k)}' value. Please set knife[:#{k}] in your knife.rb or pass as an option."
181
- end
182
- end
183
- exit 1 if errors.each { |e| ui.error(e) }.any?
184
- end
185
-
186
- # validate ASM mandatory keys
187
- def validate_asm_keys!(*keys)
188
- mandatory_keys = %i{azure_subscription_id azure_mgmt_cert azure_api_host_name}
189
- keys.concat(mandatory_keys)
190
-
191
- unless config[:azure_mgmt_cert].nil?
192
- config[:azure_mgmt_cert] = File.read find_file(config[:azure_mgmt_cert])
193
- end
194
-
195
- if !config[:azure_publish_settings_file].nil?
196
- parse_publish_settings_file(config[:azure_publish_settings_file])
197
- elsif config[:azure_subscription_id].nil? && config[:azure_mgmt_cert].nil? && config[:azure_api_host_name].nil?
198
- azureprofile_file = get_azure_profile_file_path
199
- if File.exist?(File.expand_path(azureprofile_file))
200
- errors = parse_azure_profile(azureprofile_file, errors)
201
- end
202
- end
203
- validate!(keys)
204
- end
205
-
206
- def parse_publish_settings_file(filename)
207
- require "nokogiri" unless defined?(Nokogiri)
208
- require "base64" unless defined?(Base64)
209
- require "openssl" unless defined?(OpenSSL)
210
- require "uri" unless defined?(URI)
211
- begin
212
- doc = Nokogiri::XML(File.open(find_file(filename)))
213
- profile = doc.at_css("PublishProfile")
214
- subscription = profile.at_css("Subscription")
215
- # check given PublishSettings XML file format.Currently PublishSettings file have two different XML format
216
- if profile.attribute("SchemaVersion").nil?
217
- management_cert = OpenSSL::PKCS12.new(Base64.decode64(profile.attribute("ManagementCertificate").value))
218
- config[:azure_api_host_name] = URI(profile.attribute("Url").value).host
219
- elsif profile.attribute("SchemaVersion").value == "2.0"
220
- management_cert = OpenSSL::PKCS12.new(Base64.decode64(subscription.attribute("ManagementCertificate").value))
221
- config[:azure_api_host_name] = URI(subscription.attribute("ServiceManagementUrl").value).host
222
- else
223
- ui.error("Publish settings file Schema not supported - " + filename)
224
- end
225
- config[:azure_mgmt_cert] = management_cert.certificate.to_pem + management_cert.key.to_pem
226
- config[:azure_subscription_id] = doc.at_css("Subscription").attribute("Id").value
227
- rescue
228
- ui.error("Incorrect publish settings file - " + filename)
229
- exit 1
230
- end
231
- end
232
-
233
- def get_azure_profile_file_path
234
- "~/.azure/azureProfile.json"
235
- end
236
-
237
- def parse_azure_profile(filename, errors)
238
- require "openssl" unless defined?(OpenSSL)
239
- require "uri" unless defined?(URI)
240
- errors = [] if errors.nil?
241
- azure_profile = File.read(File.expand_path(filename))
242
- azure_profile = JSON.parse(azure_profile)
243
- default_subscription = get_default_subscription(azure_profile)
244
- if default_subscription.key?("id") && default_subscription.key?("managementCertificate") && default_subscription.key?("managementEndpointUrl")
245
-
246
- config[:azure_subscription_id] = default_subscription["id"]
247
- mgmt_key = OpenSSL::PKey::RSA.new(default_subscription["managementCertificate"]["key"]).to_pem
248
- mgmt_cert = OpenSSL::X509::Certificate.new(default_subscription["managementCertificate"]["cert"]).to_pem
249
- config[:azure_mgmt_cert] = mgmt_key + mgmt_cert
250
- config[:azure_api_host_name] = URI(default_subscription["managementEndpointUrl"]).host
251
- else
252
- errors << "Check if values set for 'id', 'managementCertificate', 'managementEndpointUrl' in -> #{filename} for 'defaultSubscription'. \n OR "
253
- end
254
- errors
255
- end
256
-
257
- def get_default_subscription(azure_profile)
258
- first_subscription_as_default = nil
259
- azure_profile["subscriptions"].each do |subscription|
260
- if subscription["isDefault"]
261
- Chef::Log.info("Default subscription \'#{subscription["name"]}\'' selected.")
262
- return subscription
263
- end
264
-
265
- first_subscription_as_default ||= subscription
266
- end
267
-
268
- if first_subscription_as_default
269
- Chef::Log.info("First subscription \'#{subscription["name"]}\' selected as default.")
270
- else
271
- Chef::Log.info("No subscriptions found.")
272
- exit 1
273
- end
274
- first_subscription_as_default
275
- end
276
-
277
- def find_file(name)
278
- name = ::File.expand_path(name)
279
- config_dir = Chef::Knife.chef_config_dir
280
- if File.exist? name
281
- file = name
282
- elsif config_dir && File.exist?(File.join(config_dir, name))
283
- file = File.join(config_dir, name)
284
- elsif File.exist?(File.join(ENV["HOME"], ".chef", name))
285
- file = File.join(ENV["HOME"], ".chef", name)
286
- else
287
- ui.error("Unable to find file - " + name)
288
- exit 1
289
- end
290
- file
291
- end
292
-
293
- def fetch_deployment
294
- deployment_name = service.deployment_name(config[:azure_dns_name])
295
- service.deployment("hostedservices/#{config[:azure_dns_name]}/deployments/#{deployment_name}")
296
- end
297
-
298
- def fetch_role
299
- deployment = fetch_deployment
300
-
301
- if deployment.at_css("Deployment Name") != nil
302
- role_list_xml = deployment.css("RoleInstanceList RoleInstance")
303
- role_list_xml.each do |role|
304
- if role.at_css("RoleName").text == (config[:azure_vm_name] || @name_args[0])
305
- return role
306
- end
307
- end
308
- end
309
- nil
310
- end
311
-
312
- def fetch_extension(role)
313
- ext_list_xml = role.css("ResourceExtensionStatusList ResourceExtensionStatus")
314
- return nil if ext_list_xml.nil?
315
-
316
- ext_list_xml.each do |ext|
317
- if ext.at_css("HandlerName").text == "Chef.Bootstrap.WindowsAzure.LinuxChefClient" || ext.at_css("HandlerName").text == "Chef.Bootstrap.WindowsAzure.ChefClient"
318
- return ext
319
- end
320
- end
321
- nil
322
- end
323
-
324
- def fetch_substatus(extension)
325
- return nil if extension.at_css("ExtensionSettingStatus SubStatusList SubStatus").nil?
326
-
327
- substatus_list_xml = extension.css("ExtensionSettingStatus SubStatusList SubStatus")
328
- substatus_list_xml.each do |substatus|
329
- if substatus.at_css("Name").text == "Chef Client run logs"
330
- return substatus
331
- end
332
- end
333
- nil
334
- end
335
-
336
- def fetch_chef_client_logs(fetch_process_start_time, fetch_process_wait_timeout)
337
- ## fetch server details ##
338
- role = fetch_role
339
- if !role.nil?
340
- ## fetch Chef Extension details deployed on the server ##
341
- ext = fetch_extension(role)
342
- if !ext.nil?
343
- ## fetch substatus field which contains the chef-client run logs ##
344
- substatus = fetch_substatus(ext)
345
- if !substatus.nil?
346
- ## chef-client run logs becomes available ##
347
- name = substatus.at_css("Name").text
348
- status = substatus.at_css("Status").text
349
- message = substatus.at_css("Message").text
350
-
351
- ## printing the logs ##
352
- puts "\n\n******** Please find the chef-client run details below ********\n\n"
353
- print "----> chef-client run status: "
354
- case status
355
- when "Success"
356
- ## chef-client run succeeded ##
357
- color = :green
358
- when "Error"
359
- ## chef-client run failed ##
360
- color = :red
361
- when "Transitioning"
362
- ## chef-client run did not complete within maximum timeout of 30 minutes ##
363
- ## fetch whatever logs available under the chef-client.log file ##
364
- color = :yellow
365
- end
366
- puts "#{ui.color(status, color, :bold)}"
367
- puts "----> chef-client run logs: "
368
- puts "\n#{message}\n" ## message field of substatus contains the chef-client run logs ##
369
- else
370
- ## unavailability of the substatus field indicates that chef-client run is not completed yet on the server ##
371
- fetch_process_wait_time = ((Time.now - fetch_process_start_time) / 60).round
372
- if fetch_process_wait_time <= fetch_process_wait_timeout ## wait for maximum 30 minutes until chef-client run logs becomes available ##
373
- print "#{ui.color(".", :bold)}"
374
- sleep 30
375
- fetch_chef_client_logs(fetch_process_start_time, fetch_process_wait_timeout)
376
- else
377
- ## wait time exceeded maximum threshold set for the wait timeout ##
378
- ui.error "\nchef-client run logs could not be fetched since fetch process exceeded wait timeout of #{fetch_process_wait_timeout} minutes.\n"
379
- end
380
- end
381
- else
382
- ## Chef Extension could not be found ##
383
- ui.error("Unable to find Chef extension under role #{config[:azure_vm_name] || @name_args[0]}.")
384
- end
385
- else
386
- ## server could not be found ##
387
- ui.error("chef-client run logs could not be fetched since role #{config[:azure_vm_name] || @name_args[0]} could not be found.")
388
- end
389
- end
390
- end
391
- end
392
- end