knife-azure 1.6.0.rc.0 → 1.6.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +304 -8
  3. data/lib/azure/azure_interface.rb +81 -0
  4. data/lib/azure/custom_errors.rb +35 -0
  5. data/lib/azure/helpers.rb +44 -0
  6. data/lib/azure/resource_management/ARM_base.rb +29 -0
  7. data/lib/azure/resource_management/ARM_deployment_template.rb +561 -0
  8. data/lib/azure/resource_management/ARM_interface.rb +795 -0
  9. data/lib/azure/resource_management/windows_credentials.rb +136 -0
  10. data/lib/azure/service_management/ASM_interface.rb +301 -0
  11. data/lib/azure/{ag.rb → service_management/ag.rb} +2 -2
  12. data/lib/azure/{certificate.rb → service_management/certificate.rb} +2 -2
  13. data/lib/azure/service_management/connection.rb +102 -0
  14. data/lib/azure/{deploy.rb → service_management/deploy.rb} +8 -2
  15. data/lib/azure/{disk.rb → service_management/disk.rb} +2 -2
  16. data/lib/azure/{host.rb → service_management/host.rb} +2 -2
  17. data/lib/azure/{image.rb → service_management/image.rb} +2 -2
  18. data/lib/azure/{loadbalancer.rb → service_management/loadbalancer.rb} +4 -18
  19. data/lib/azure/{rest.rb → service_management/rest.rb} +15 -10
  20. data/lib/azure/{role.rb → service_management/role.rb} +174 -6
  21. data/lib/azure/{storageaccount.rb → service_management/storageaccount.rb} +2 -2
  22. data/lib/azure/{utility.rb → service_management/utility.rb} +0 -0
  23. data/lib/azure/{vnet.rb → service_management/vnet.rb} +2 -2
  24. data/lib/chef/knife/azure_ag_create.rb +3 -6
  25. data/lib/chef/knife/azure_ag_list.rb +2 -16
  26. data/lib/chef/knife/azure_base.rb +89 -22
  27. data/lib/chef/knife/azure_image_list.rb +3 -7
  28. data/lib/chef/knife/azure_internal-lb_create.rb +2 -5
  29. data/lib/chef/knife/azure_internal-lb_list.rb +2 -16
  30. data/lib/chef/knife/azure_server_create.rb +122 -501
  31. data/lib/chef/knife/azure_server_delete.rb +15 -38
  32. data/lib/chef/knife/azure_server_list.rb +2 -27
  33. data/lib/chef/knife/azure_server_show.rb +4 -60
  34. data/lib/chef/knife/azure_vnet_create.rb +2 -7
  35. data/lib/chef/knife/azure_vnet_list.rb +2 -17
  36. data/lib/chef/knife/azurerm_base.rb +228 -0
  37. data/lib/chef/knife/azurerm_server_create.rb +393 -0
  38. data/lib/chef/knife/azurerm_server_delete.rb +121 -0
  39. data/lib/chef/knife/azurerm_server_list.rb +18 -0
  40. data/lib/chef/knife/azurerm_server_show.rb +37 -0
  41. data/lib/chef/knife/bootstrap/bootstrap_options.rb +105 -0
  42. data/lib/chef/knife/bootstrap/bootstrapper.rb +343 -0
  43. data/lib/chef/knife/bootstrap/common_bootstrap_options.rb +116 -0
  44. data/lib/chef/knife/bootstrap_azure.rb +110 -0
  45. data/lib/chef/knife/bootstrap_azurerm.rb +116 -0
  46. data/lib/knife-azure/version.rb +1 -2
  47. metadata +132 -16
  48. data/lib/azure/connection.rb +0 -99
@@ -71,11 +71,11 @@ class Chef
71
71
  option :azure_dns_name,
72
72
  :long => "--azure-dns-name NAME",
73
73
  :description => "specifies the DNS name (also known as hosted service name)"
74
-
74
+
75
75
  option :wait,
76
76
  :long => "--wait",
77
77
  :boolean => true,
78
- :default => false,
78
+ :default => false,
79
79
  :description => "Wait for server deletion. Default is false"
80
80
 
81
81
  # Extracted from Chef::Knife.delete_object, because it has a
@@ -103,46 +103,24 @@ class Chef
103
103
  end
104
104
 
105
105
  def run
106
-
107
- validate!
106
+ validate_asm_keys!
108
107
  validate_disk_and_storage
109
108
  @name_args.each do |name|
110
-
111
109
  begin
112
- server = connection.roles.find(name, params = { :azure_dns_name => locate_config_value(:azure_dns_name) })
113
-
114
- if not server
115
- ui.warn("Server #{name} does not exist")
116
- return
117
- end
118
- puts "\n"
119
- msg_pair('DNS Name', server.hostedservicename + ".cloudapp.net")
120
- msg_pair('VM Name', server.name)
121
- msg_pair('Size', server.size)
122
- msg_pair('Public Ip Address', server.publicipaddress)
123
- puts "\n"
124
-
125
- begin
126
- confirm("Do you really want to delete this server")
127
- rescue SystemExit # Need to handle this as confirming with N/n raises SystemExit exception
128
- server = nil # Cleanup is implicitly performed in other cloud plugins
129
- exit!
130
- end
131
-
132
- connection.roles.delete(name, params = { :preserve_azure_os_disk => locate_config_value(:preserve_azure_os_disk),
133
- :preserve_azure_vhd => locate_config_value(:preserve_azure_vhd),
134
- :preserve_azure_dns_name => locate_config_value(:preserve_azure_dns_name),
135
- :azure_dns_name => server.hostedservicename,
136
- :delete_azure_storage_account => locate_config_value(:delete_azure_storage_account),
137
- :wait => locate_config_value(:wait) })
138
-
139
- puts "\n"
140
- ui.warn("Deleted server #{server.name}")
110
+ service.delete_server( { name: name, preserve_azure_os_disk: locate_config_value(:preserve_azure_os_disk),
111
+ preserve_azure_vhd: locate_config_value(:preserve_azure_vhd),
112
+ preserve_azure_dns_name: locate_config_value(:preserve_azure_dns_name),
113
+ delete_azure_storage_account: locate_config_value(:delete_azure_storage_account),
114
+ wait: locate_config_value(:wait) } )
141
115
 
142
116
  if config[:purge]
143
- thing_to_delete = config[:chef_node_name] || name
144
- destroy_item(Chef::Node, thing_to_delete, "node")
145
- destroy_item(Chef::ApiClient, thing_to_delete, "client")
117
+ node_to_delete = config[:chef_node_name] || name
118
+ if node_to_delete
119
+ destroy_item(Chef::Node, node_to_delete, 'node')
120
+ destroy_item(Chef::ApiClient, node_to_delete, 'client')
121
+ else
122
+ ui.warn("Node name to purge not provided. Corresponding client node will remain on Chef Server.")
123
+ end
146
124
  else
147
125
  ui.warn("Corresponding node and client for the #{name} server were not deleted and remain registered with the Chef Server")
148
126
  end
@@ -153,7 +131,6 @@ class Chef
153
131
  end
154
132
  end
155
133
  end
156
-
157
134
  end
158
135
  end
159
136
  end
@@ -30,33 +30,8 @@ class Chef
30
30
 
31
31
  def run
32
32
  $stdout.sync = true
33
-
34
- validate!
35
-
36
- server_labels = ['DNS Name', 'VM Name', 'Status', 'IP Address', 'SSH Port', 'WinRM Port' ]
37
- server_list = server_labels.map {|label| ui.color(label, :bold)}
38
- items = connection.roles.all
39
-
40
- items.each do |server|
41
- server_list << server.hostedservicename.to_s+".cloudapp.net" # Info about the DNS name at http://msdn.microsoft.com/en-us/library/ee460806.aspx
42
- server_list << server.name.to_s
43
- server_list << begin
44
- state = server.status.to_s.downcase
45
- case state
46
- when 'shutting-down','terminated','stopping','stopped'
47
- ui.color(state, :red)
48
- when 'pending'
49
- ui.color(state, :yellow)
50
- else
51
- ui.color('ready', :green)
52
- end
53
- end
54
- server_list << server.publicipaddress.to_s
55
- server_list << server.sshport.to_s
56
- server_list << server.winrmport.to_s
57
- end
58
- puts ''
59
- puts ui.list(server_list, :uneven_columns_across, 6)
33
+ validate_asm_keys!
34
+ service.list_servers
60
35
  end
61
36
  end
62
37
  end
@@ -26,72 +26,16 @@ class Chef
26
26
 
27
27
  include Knife::AzureBase
28
28
 
29
- banner "knife azure server show SERVER [SERVER]"
29
+ banner "knife azure server show SERVER [SERVER]"
30
30
 
31
31
  def run
32
32
  $stdout.sync = true
33
-
34
- validate!
35
-
33
+ validate_asm_keys!
36
34
  @name_args.each do |name|
37
- role = connection.roles.find name
38
- puts ''
39
- if (role)
40
- details = Array.new
41
- details << ui.color('Role name', :bold, :cyan)
42
- details << role.name
43
- details << ui.color('Status', :bold, :cyan)
44
- details << role.status
45
- details << ui.color('Size', :bold, :cyan)
46
- details << role.size
47
- details << ui.color('Hosted service name', :bold, :cyan)
48
- details << role.hostedservicename
49
- details << ui.color('Deployment name', :bold, :cyan)
50
- details << role.deployname
51
- details << ui.color('Host name', :bold, :cyan)
52
- details << role.hostname
53
- unless role.sshport.nil?
54
- details << ui.color('SSH port', :bold, :cyan)
55
- details << role.sshport
56
- end
57
- unless role.winrmport.nil?
58
- details << ui.color('WinRM port', :bold, :cyan)
59
- details << role.winrmport
60
- end
61
- details << ui.color('Public IP', :bold, :cyan)
62
- details << role.publicipaddress
63
- unless role.thumbprint.empty?
64
- details << ui.color('Thumbprint', :bold, :cyan)
65
- details << role.thumbprint
66
- end
67
- puts ui.list(details, :columns_across, 2)
68
- if role.tcpports.length > 0 || role.udpports.length > 0
69
- details.clear
70
- details << ui.color('Ports open', :bold, :cyan)
71
- details << ui.color('Local port', :bold, :cyan)
72
- details << ui.color('IP', :bold, :cyan)
73
- details << ui.color('Public port', :bold, :cyan)
74
- if role.tcpports.length > 0
75
- role.tcpports.each do |port|
76
- details << 'tcp'
77
- details << port['LocalPort']
78
- details << port['Vip']
79
- details << port['PublicPort']
80
- end
81
- end
82
- if role.udpports.length > 0
83
- role.udpports.each do |port|
84
- details << 'udp'
85
- details << port['LocalPort']
86
- details << port['Vip']
87
- details << port['PublicPort']
88
- end
89
- end
90
- puts ui.list(details, :columns_across, 4)
91
- end
92
- end
35
+ service.show_server name
93
36
  end
94
37
  end
38
+
95
39
  end
96
40
  end
97
41
  end
@@ -51,12 +51,7 @@ class Chef
51
51
  $stdout.sync = true
52
52
 
53
53
  Chef::Log.info('validating...')
54
- validate!([:azure_subscription_id,
55
- :azure_mgmt_cert,
56
- :azure_api_host_name,
57
- :azure_network_name,
58
- :azure_affinity_group,
59
- :azure_address_space])
54
+ validate_asm_keys!(:azure_network_name, :azure_affinity_group, :azure_address_space)
60
55
 
61
56
  params = {
62
57
  azure_vnet_name: locate_config_value(:azure_network_name),
@@ -65,7 +60,7 @@ class Chef
65
60
  azure_subnet_name: locate_config_value(:azure_subnet_name) || "Subnet-#{Random.rand(10)}"
66
61
  }
67
62
 
68
- rsp = connection.vnets.create(params)
63
+ rsp = service.create_vnet(params)
69
64
  print "\n"
70
65
  if rsp.at_css('Status').nil?
71
66
  if rsp.at_css('Code').nil? || rsp.at_css('Message').nil?
@@ -27,25 +27,10 @@ class Chef
27
27
 
28
28
  banner 'knife azure vnet list (options)'
29
29
 
30
- def hl
31
- @highline ||= HighLine.new
32
- end
33
-
34
30
  def run
35
31
  $stdout.sync = true
36
-
37
- validate!
38
-
39
- cols = ['Name', 'Affinity Group', 'State']
40
- the_list = cols.map { |col| ui.color(col, :bold) }
41
- connection.vnets.all.each do |vnet|
42
- %w(name affinity_group state).each do |attr|
43
- the_list << vnet.send(attr).to_s
44
- end
45
- end
46
-
47
- puts "\n"
48
- puts hl.list(the_list, :uneven_columns_across, cols.size)
32
+ validate_asm_keys!
33
+ service.list_vnets
49
34
  end
50
35
  end
51
36
  end
@@ -0,0 +1,228 @@
1
+
2
+ # Author:: Aliasgar Batterywala (aliasgar.batterywala@clogeny.com)
3
+ #
4
+ # Copyright:: Copyright (c) 2016 Opscode, Inc.
5
+ # License:: Apache License, Version 2.0
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ require 'chef/knife'
21
+ require 'azure/resource_management/ARM_interface'
22
+ require 'mixlib/shellout'
23
+ require 'time'
24
+
25
+ class Chef
26
+ class Knife
27
+ module AzurermBase
28
+
29
+ if Chef::Platform.windows?
30
+ require 'azure/resource_management/windows_credentials'
31
+ include Azure::ARM::WindowsCredentials
32
+ end
33
+
34
+ def self.included(includer)
35
+ includer.class_eval do
36
+
37
+ deps do
38
+ require 'readline'
39
+ require 'chef/json_compat'
40
+ end
41
+
42
+ option :azure_resource_group_name,
43
+ :short => "-r RESOURCE_GROUP_NAME",
44
+ :long => "--azure-resource-group-name RESOURCE_GROUP_NAME",
45
+ :description => "The Resource Group name."
46
+
47
+ end
48
+ end
49
+
50
+ def service
51
+ details = authentication_details()
52
+ details.update(:azure_subscription_id => locate_config_value(:azure_subscription_id))
53
+ @service ||= begin
54
+ service = Azure::ResourceManagement::ARMInterface.new(details)
55
+ end
56
+ @service.ui = ui
57
+ @service
58
+ end
59
+
60
+ def locate_config_value(key)
61
+ key = key.to_sym
62
+ config[key] || Chef::Config[:knife][key] || default_config[key]
63
+ end
64
+
65
+ # validates ARM mandatory keys
66
+ def validate_arm_keys!(*keys)
67
+ Chef::Log.warn('Azurerm subcommands are experimental and of alpha quality. Not suitable for production use. Please use ASM subcommands for production.')
68
+ parse_publish_settings_file(locate_config_value(:azure_publish_settings_file)) if(locate_config_value(:azure_publish_settings_file) != nil)
69
+ keys.push(:azure_subscription_id)
70
+
71
+ if(locate_config_value(:azure_tenant_id).nil? || locate_config_value(:azure_client_id).nil? || locate_config_value(:azure_client_secret).nil?)
72
+ validate_azure_login
73
+ else
74
+ keys.concat([:azure_tenant_id, :azure_client_id, :azure_client_secret])
75
+ end
76
+
77
+ errors = []
78
+ keys.each do |k|
79
+ if locate_config_value(k).nil?
80
+ errors << "You did not provide a valid '#{pretty_key(k)}' value. Please set knife[:#{k}] in your knife.rb."
81
+ end
82
+ end
83
+ if errors.each{|e| ui.error(e)}.any?
84
+ exit 1
85
+ end
86
+ end
87
+
88
+ def authentication_details
89
+ if(!locate_config_value(:azure_tenant_id).nil? && !locate_config_value(:azure_client_id).nil? && !locate_config_value(:azure_client_secret).nil?)
90
+ return {:azure_tenant_id => locate_config_value(:azure_tenant_id), :azure_client_id => locate_config_value(:azure_client_id), :azure_client_secret => locate_config_value(:azure_client_secret)}
91
+ elsif Chef::Platform.windows?
92
+ token_details = token_details_for_windows()
93
+ else
94
+ token_details = token_details_for_linux()
95
+ end
96
+ check_token_validity(token_details)
97
+ return token_details
98
+ end
99
+
100
+ def token_details_for_linux
101
+ home_dir = File.expand_path('~')
102
+ file = File.read(home_dir + '/.azure/accessTokens.json')
103
+ file = eval(file)
104
+ token_details = {:tokentype => file[-1][:tokenType], :user => file[-1][:userId], :token => file[-1][:accessToken], :clientid => file[-1][:_clientId], :expiry_time => file[-1][:expiresOn], :refreshtoken => file[-1][:refreshToken]}
105
+ return token_details
106
+ end
107
+
108
+ def check_token_validity(token_details)
109
+ time_difference = Time.parse(token_details[:expiry_time]) - Time.now.utc
110
+ if time_difference <= 0
111
+ raise "Token has expired, please run any azure command like azure vm list to get new token from refresh token else run azure login command"
112
+ end
113
+ end
114
+
115
+ def validate_azure_login
116
+ err_string = "Please run XPLAT's 'azure login' command OR specify azure_tenant_id, azure_subscription_id, azure_client_id, azure_client_secret in your knife.rb"
117
+ if Chef::Platform.windows?
118
+ # cmdkey command is used for accessing windows credential manager
119
+ xplat_creds_cmd = Mixlib::ShellOut.new("cmdkey /list | findstr AzureXplatCli")
120
+ result = xplat_creds_cmd.run_command
121
+ if result.stdout.nil? || result.stdout.empty?
122
+ raise err_string
123
+ end
124
+ else
125
+ home_dir = File.expand_path('~')
126
+ if !File.exists?(home_dir + "/.azure/accessTokens.json") || File.size?(home_dir + '/.azure/accessTokens.json') <= 2
127
+ raise err_string
128
+ end
129
+ end
130
+ end
131
+
132
+ def parse_publish_settings_file(filename)
133
+ require 'nokogiri'
134
+ require 'base64'
135
+ require 'openssl'
136
+ require 'uri'
137
+ begin
138
+ doc = Nokogiri::XML(File.open(find_file(filename)))
139
+ profile = doc.at_css("PublishProfile")
140
+ subscription = profile.at_css("Subscription")
141
+ #check given PublishSettings XML file format.Currently PublishSettings file have two different XML format
142
+ if profile.attribute("SchemaVersion").nil?
143
+ management_cert = OpenSSL::PKCS12.new(Base64.decode64(profile.attribute("ManagementCertificate").value))
144
+ Chef::Config[:knife][:azure_api_host_name] = URI(profile.attribute("Url").value).host
145
+ elsif profile.attribute("SchemaVersion").value == "2.0"
146
+ management_cert = OpenSSL::PKCS12.new(Base64.decode64(subscription.attribute("ManagementCertificate").value))
147
+ Chef::Config[:knife][:azure_api_host_name] = URI(subscription.attribute("ServiceManagementUrl").value).host
148
+ else
149
+ ui.error("Publish settings file Schema not supported - " + filename)
150
+ end
151
+ Chef::Config[:knife][:azure_mgmt_cert] = management_cert.certificate.to_pem + management_cert.key.to_pem
152
+ Chef::Config[:knife][:azure_subscription_id] = doc.at_css("Subscription").attribute("Id").value
153
+ rescue=> error
154
+ puts "#{error.class} and #{error.message}"
155
+ exit 1
156
+ end
157
+ end
158
+
159
+ def find_file(name)
160
+ config_dir = Chef::Knife.chef_config_dir
161
+ if File.exist? name
162
+ file = name
163
+ elsif config_dir && File.exist?(File.join(config_dir, name))
164
+ file = File.join(config_dir, name)
165
+ elsif File.exist?(File.join(ENV['HOME'], '.chef', name))
166
+ file = File.join(ENV['HOME'], '.chef', name)
167
+ else
168
+ ui.error('Unable to find file - ' + name)
169
+ exit 1
170
+ end
171
+ file
172
+ end
173
+
174
+ def pretty_key(key)
175
+ key.to_s.gsub(/_/, ' ').gsub(/\w+/){ |w| (w =~ /(ssh)|(aws)/i) ? w.upcase : w.capitalize }
176
+ end
177
+
178
+ def is_image_windows?
179
+ locate_config_value(:azure_image_reference_offer) =~ /WindowsServer.*/
180
+ end
181
+
182
+ def msg_pair(label, value, color=:cyan)
183
+ if value && !value.to_s.empty?
184
+ puts "#{ui.color(label, color)}: #{value}"
185
+ end
186
+ end
187
+
188
+ def msg_server_summary(server)
189
+ puts "\n\n"
190
+ if server.provisioningstate == 'Succeeded'
191
+ Chef::Log.info("Server creation went successfull.")
192
+ puts "\nServer Details are:\n"
193
+
194
+ msg_pair('Server ID', server.id)
195
+ msg_pair('Server Name', server.name)
196
+ msg_pair('Server Public IP Address', server.publicipaddress)
197
+ if is_image_windows?
198
+ msg_pair('Server RDP Port', server.rdpport)
199
+ else
200
+ msg_pair('Server SSH Port', server.sshport)
201
+ end
202
+ msg_pair('Server Location', server.locationname)
203
+ msg_pair('Server OS Type', server.ostype)
204
+ msg_pair('Server Provisioning State', server.provisioningstate)
205
+ else
206
+ Chef::Log.info("Server Creation Failed.")
207
+ end
208
+
209
+ puts "\n\n"
210
+
211
+ if server.resources.provisioning_state == 'Succeeded'
212
+ Chef::Log.info("Server Extension creation went successfull.")
213
+ puts "\nServer Extension Details are:\n"
214
+
215
+ msg_pair('Server Extension ID', server.resources.id)
216
+ msg_pair('Server Extension Name', server.resources.name)
217
+ msg_pair('Server Extension Publisher', server.resources.publisher)
218
+ msg_pair('Server Extension Type', server.resources.type)
219
+ msg_pair('Server Extension Type Handler Version', server.resources.type_handler_version)
220
+ msg_pair('Server Extension Provisioning State', server.resources.provisioning_state)
221
+ else
222
+ Chef::Log.info("Server Extension Creation Failed.")
223
+ end
224
+ puts "\n"
225
+ end
226
+ end
227
+ end
228
+ end