knife-azure 1.6.0.rc.0 → 1.6.0

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