knife-azure 3.0.6 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/azure/custom_errors.rb +1 -1
- data/lib/azure/resource_management/ARM_deployment_template.rb +5 -5
- data/lib/azure/resource_management/ARM_interface.rb +4 -6
- data/lib/azure/resource_management/windows_credentials.rb +2 -2
- data/lib/chef/knife/azurerm_server_create.rb +1 -1
- data/lib/chef/knife/bootstrap/bootstrapper.rb +5 -10
- data/lib/chef/knife/helpers/azurerm_base.rb +4 -4
- data/lib/knife-azure/version.rb +1 -1
- metadata +30 -43
- data/lib/azure/service_management/ASM_interface.rb +0 -310
- data/lib/azure/service_management/ag.rb +0 -99
- data/lib/azure/service_management/certificate.rb +0 -235
- data/lib/azure/service_management/connection.rb +0 -102
- data/lib/azure/service_management/deploy.rb +0 -221
- data/lib/azure/service_management/disk.rb +0 -68
- data/lib/azure/service_management/host.rb +0 -184
- data/lib/azure/service_management/image.rb +0 -94
- data/lib/azure/service_management/loadbalancer.rb +0 -78
- data/lib/azure/service_management/rest.rb +0 -126
- data/lib/azure/service_management/role.rb +0 -717
- data/lib/azure/service_management/storageaccount.rb +0 -127
- data/lib/azure/service_management/utility.rb +0 -40
- data/lib/azure/service_management/vnet.rb +0 -134
- data/lib/chef/knife/azure_ag_create.rb +0 -73
- data/lib/chef/knife/azure_ag_list.rb +0 -35
- data/lib/chef/knife/azure_image_list.rb +0 -56
- data/lib/chef/knife/azure_internal-lb_create.rb +0 -74
- data/lib/chef/knife/azure_internal-lb_list.rb +0 -35
- data/lib/chef/knife/azure_server_create.rb +0 -531
- data/lib/chef/knife/azure_server_delete.rb +0 -136
- data/lib/chef/knife/azure_server_list.rb +0 -38
- data/lib/chef/knife/azure_server_show.rb +0 -41
- data/lib/chef/knife/azure_vnet_create.rb +0 -74
- data/lib/chef/knife/azure_vnet_list.rb +0 -35
- data/lib/chef/knife/bootstrap_azure.rb +0 -191
- 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
|