knife-azure 3.0.0 → 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.
- 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 +8 -12
- data/lib/azure/resource_management/windows_credentials.rb +7 -8
- data/lib/chef/knife/azurerm_server_create.rb +2 -2
- data/lib/chef/knife/azurerm_server_delete.rb +1 -1
- data/lib/chef/knife/bootstrap/bootstrapper.rb +10 -11
- data/lib/chef/knife/bootstrap_azurerm.rb +1 -1
- data/lib/chef/knife/helpers/azurerm_base.rb +17 -19
- 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 -125
- 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 -394
@@ -1,394 +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"
|
208
|
-
require "base64"
|
209
|
-
require "openssl"
|
210
|
-
require "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"
|
239
|
-
require "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
|
-
deployment = service.deployment("hostedservices/#{config[:azure_dns_name]}/deployments/#{deployment_name}")
|
296
|
-
|
297
|
-
deployment
|
298
|
-
end
|
299
|
-
|
300
|
-
def fetch_role
|
301
|
-
deployment = fetch_deployment
|
302
|
-
|
303
|
-
if deployment.at_css("Deployment Name") != nil
|
304
|
-
role_list_xml = deployment.css("RoleInstanceList RoleInstance")
|
305
|
-
role_list_xml.each do |role|
|
306
|
-
if role.at_css("RoleName").text == (config[:azure_vm_name] || @name_args[0])
|
307
|
-
return role
|
308
|
-
end
|
309
|
-
end
|
310
|
-
end
|
311
|
-
nil
|
312
|
-
end
|
313
|
-
|
314
|
-
def fetch_extension(role)
|
315
|
-
ext_list_xml = role.css("ResourceExtensionStatusList ResourceExtensionStatus")
|
316
|
-
return nil if ext_list_xml.nil?
|
317
|
-
|
318
|
-
ext_list_xml.each do |ext|
|
319
|
-
if ext.at_css("HandlerName").text == "Chef.Bootstrap.WindowsAzure.LinuxChefClient" || ext.at_css("HandlerName").text == "Chef.Bootstrap.WindowsAzure.ChefClient"
|
320
|
-
return ext
|
321
|
-
end
|
322
|
-
end
|
323
|
-
nil
|
324
|
-
end
|
325
|
-
|
326
|
-
def fetch_substatus(extension)
|
327
|
-
return nil if extension.at_css("ExtensionSettingStatus SubStatusList SubStatus").nil?
|
328
|
-
|
329
|
-
substatus_list_xml = extension.css("ExtensionSettingStatus SubStatusList SubStatus")
|
330
|
-
substatus_list_xml.each do |substatus|
|
331
|
-
if substatus.at_css("Name").text == "Chef Client run logs"
|
332
|
-
return substatus
|
333
|
-
end
|
334
|
-
end
|
335
|
-
nil
|
336
|
-
end
|
337
|
-
|
338
|
-
def fetch_chef_client_logs(fetch_process_start_time, fetch_process_wait_timeout)
|
339
|
-
## fetch server details ##
|
340
|
-
role = fetch_role
|
341
|
-
if !role.nil?
|
342
|
-
## fetch Chef Extension details deployed on the server ##
|
343
|
-
ext = fetch_extension(role)
|
344
|
-
if !ext.nil?
|
345
|
-
## fetch substatus field which contains the chef-client run logs ##
|
346
|
-
substatus = fetch_substatus(ext)
|
347
|
-
if !substatus.nil?
|
348
|
-
## chef-client run logs becomes available ##
|
349
|
-
name = substatus.at_css("Name").text
|
350
|
-
status = substatus.at_css("Status").text
|
351
|
-
message = substatus.at_css("Message").text
|
352
|
-
|
353
|
-
## printing the logs ##
|
354
|
-
puts "\n\n******** Please find the chef-client run details below ********\n\n"
|
355
|
-
print "----> chef-client run status: "
|
356
|
-
case status
|
357
|
-
when "Success"
|
358
|
-
## chef-client run succeeded ##
|
359
|
-
color = :green
|
360
|
-
when "Error"
|
361
|
-
## chef-client run failed ##
|
362
|
-
color = :red
|
363
|
-
when "Transitioning"
|
364
|
-
## chef-client run did not complete within maximum timeout of 30 minutes ##
|
365
|
-
## fetch whatever logs available under the chef-client.log file ##
|
366
|
-
color = :yellow
|
367
|
-
end
|
368
|
-
puts "#{ui.color(status, color, :bold)}"
|
369
|
-
puts "----> chef-client run logs: "
|
370
|
-
puts "\n#{message}\n" ## message field of substatus contains the chef-client run logs ##
|
371
|
-
else
|
372
|
-
## unavailability of the substatus field indicates that chef-client run is not completed yet on the server ##
|
373
|
-
fetch_process_wait_time = ((Time.now - fetch_process_start_time) / 60).round
|
374
|
-
if fetch_process_wait_time <= fetch_process_wait_timeout ## wait for maximum 30 minutes until chef-client run logs becomes available ##
|
375
|
-
print "#{ui.color(".", :bold)}"
|
376
|
-
sleep 30
|
377
|
-
fetch_chef_client_logs(fetch_process_start_time, fetch_process_wait_timeout)
|
378
|
-
else
|
379
|
-
## wait time exceeded maximum threshold set for the wait timeout ##
|
380
|
-
ui.error "\nchef-client run logs could not be fetched since fetch process exceeded wait timeout of #{fetch_process_wait_timeout} minutes.\n"
|
381
|
-
end
|
382
|
-
end
|
383
|
-
else
|
384
|
-
## Chef Extension could not be found ##
|
385
|
-
ui.error("Unable to find Chef extension under role #{config[:azure_vm_name] || @name_args[0]}.")
|
386
|
-
end
|
387
|
-
else
|
388
|
-
## server could not be found ##
|
389
|
-
ui.error("chef-client run logs could not be fetched since role #{config[:azure_vm_name] || @name_args[0]} could not be found.")
|
390
|
-
end
|
391
|
-
end
|
392
|
-
end
|
393
|
-
end
|
394
|
-
end
|