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
@@ -0,0 +1,136 @@
1
+ #
2
+ # Author:: Nimisha Sharad (nimisha.sharad@clogeny.com)
3
+ # Copyright:: Copyright (c) 2015-2016 Opscode, 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
+ # XPLAT stores the access token and other information in windows credential manager.
20
+ # Using FFI to call CredRead function
21
+ require 'chef'
22
+ require 'mixlib/shellout'
23
+ require 'ffi'
24
+
25
+ module Azure::ARM
26
+
27
+ module ReadCred
28
+
29
+ extend FFI::Library
30
+
31
+ ffi_lib 'Advapi32'
32
+
33
+ CRED_TYPE_GENERIC = 1
34
+ CRED_TYPE_DOMAIN_PASSWORD = 2
35
+ CRED_TYPE_DOMAIN_CERTIFICATE = 3
36
+ CRED_TYPE_DOMAIN_VISIBLE_PASSWORD = 4
37
+
38
+ # Ref: https://msdn.microsoft.com/en-us/library/windows/desktop/ms724284(v=vs.85).aspx
39
+ class FILETIME < FFI::Struct
40
+ layout :dwLowDateTime, :uint32,
41
+ :dwHighDateTime, :uint32
42
+ end
43
+
44
+ # Ref: https://msdn.microsoft.com/en-us/library/windows/desktop/aa374790(v=vs.85).aspx
45
+ class CREDENTIAL_ATTRIBUTE < FFI::Struct
46
+ layout :Keyword, :pointer,
47
+ :Flags, :uint32,
48
+ :ValueSize, :uint32,
49
+ :Value, :pointer
50
+ end
51
+
52
+ # Ref: https://msdn.microsoft.com/en-us/library/windows/desktop/aa374788(v=vs.85).aspx
53
+ class CREDENTIAL_OBJECT < FFI::Struct
54
+ layout :Flags, :uint32,
55
+ :Type, :uint32,
56
+ :TargetName, :pointer,
57
+ :Comment, :pointer,
58
+ :LastWritten, FILETIME,
59
+ :CredentialBlobSize, :uint32,
60
+ :CredentialBlob, :pointer,
61
+ :Persist, :uint32,
62
+ :AttributeCount, :uint32,
63
+ :Attributes, CREDENTIAL_ATTRIBUTE,
64
+ :TargetAlias, :pointer,
65
+ :UserName, :pointer
66
+ end
67
+
68
+ # Ref: https://msdn.microsoft.com/en-us/library/windows/desktop/aa374804(v=vs.85).aspx
69
+ attach_function :CredReadW, [:pointer, :uint32, :uint32, :pointer], :bool
70
+ end
71
+
72
+
73
+ module WindowsCredentials
74
+
75
+ include Chef::Mixin::WideString
76
+ include ReadCred
77
+
78
+ def token_details_for_windows
79
+ target = target_name
80
+
81
+ if target
82
+ target_pointer = wstring(target)
83
+ info_ptr = FFI::MemoryPointer.new(:pointer)
84
+ cred = CREDENTIAL_OBJECT.new info_ptr
85
+ cred_result = CredReadW(target_pointer, CRED_TYPE_GENERIC, 0, cred)
86
+ translated_cred = CREDENTIAL_OBJECT.new(info_ptr.read_pointer)
87
+
88
+ target_obj = translated_cred[:TargetName].read_wstring.split("::") if translated_cred[:TargetName].read_wstring
89
+ cred_blob = translated_cred[:CredentialBlob].get_bytes(0, translated_cred[:CredentialBlobSize]).split("::")
90
+
91
+ tokentype = target_obj.select { |obj| obj.include? "tokenType" }
92
+ user = target_obj.select { |obj| obj.include? "userId" }
93
+ clientid = target_obj.select { |obj| obj.include? "clientId" }
94
+ expiry_time = target_obj.select { |obj| obj.include? "expiresOn" }
95
+ access_token = cred_blob.select { |obj| obj.include? "a:" }
96
+ refresh_token = cred_blob.select { |obj| obj.include? "r:" }
97
+
98
+ credential = {}
99
+ credential[:tokentype] = tokentype[0].split(":")[1] if tokentype
100
+ credential[:user] = user[0].split(":")[1] if user
101
+ credential[:token] = access_token[0].split(":")[1] if access_token
102
+ credential[:refresh_token] = refresh_token[0].split(":")[1] if refresh_token
103
+ credential[:clientid] = clientid[0].split(":")[1] if clientid
104
+ credential[:expiry_time] = expiry_time[0].split("expiresOn:")[1].gsub("\\","") if expiry_time
105
+ else
106
+ raise "TargetName Not Found"
107
+ end
108
+ credential
109
+ end
110
+
111
+ def target_name
112
+ # cmdkey command is used for accessing windows credential manager
113
+ xplat_creds_cmd = Mixlib::ShellOut.new("cmdkey /list | findstr AzureXplatCli")
114
+ result = xplat_creds_cmd.run_command
115
+
116
+ target_name = ""
117
+ if result.stdout.empty?
118
+ raise "Azure Credentials not found. Please run xplat's 'azure login' command"
119
+ else
120
+ result.stdout.split("\n").each do |target|
121
+ # Three credentials get created in windows credential manager for xplat-cli
122
+ # One of them is for common tanent id, which can't be used
123
+ # Two of them end with --0-2 and --1-2. The one ending with --1-2 doesn't have
124
+ # accessToken and refreshToken in the credentialBlob.
125
+ # Selecting the one ending with --0-2
126
+ if !target.include?("common::") && target.include?("--0-2")
127
+ target_name = target.gsub("Target:","").strip
128
+ break
129
+ end
130
+ end
131
+ end
132
+
133
+ target_name
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,301 @@
1
+ #
2
+ # Author::
3
+ # Copyright:: Copyright (c) 2016 Opscode, 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 'azure/azure_interface'
20
+ require 'azure/service_management/rest'
21
+ require 'azure/service_management/connection'
22
+
23
+ module Azure
24
+ class ServiceManagement
25
+ class ASMInterface < AzureInterface
26
+ include AzureAPI
27
+
28
+ attr_accessor :connection
29
+
30
+ def initialize(params = {})
31
+ @rest = Rest.new(params)
32
+ @connection = Azure::ServiceManagement::Connection.new(@rest)
33
+ super
34
+ end
35
+
36
+ def list_images
37
+ connection.images.all
38
+ end
39
+
40
+ def list_servers
41
+ servers = connection.roles.all
42
+ cols = ['DNS Name', 'VM Name', 'Status', 'IP Address', 'SSH Port', 'WinRM Port' ]
43
+ rows = []
44
+ servers.each do |server|
45
+ rows << server.hostedservicename.to_s+".cloudapp.net" # Info about the DNS name at http://msdn.microsoft.com/en-us/library/ee460806.aspx
46
+ rows << server.name.to_s
47
+ rows << begin
48
+ state = server.status.to_s.downcase
49
+ case state
50
+ when 'shutting-down','terminated','stopping','stopped'
51
+ ui.color(state, :red)
52
+ when 'pending'
53
+ ui.color(state, :yellow)
54
+ else
55
+ ui.color('ready', :green)
56
+ end
57
+ end
58
+ rows << server.publicipaddress.to_s
59
+ rows << server.sshport.to_s
60
+ rows << server.winrmport.to_s
61
+ end
62
+ display_list(ui, cols, rows)
63
+ end
64
+
65
+ def find_server(params = {})
66
+ server = connection.roles.find(params[:name], params = { :azure_dns_name => params[:azure_dns_name] })
67
+ end
68
+
69
+ def delete_server(params = {})
70
+ server = find_server({name: params[:name], azure_dns_name: params[:azure_dns_name]})
71
+
72
+ unless server
73
+ puts "\n"
74
+ ui.error("Server #{params[:name]} does not exist")
75
+ exit!
76
+ end
77
+
78
+ puts "\n"
79
+ msg_pair(ui, 'DNS Name', server.hostedservicename + ".cloudapp.net")
80
+ msg_pair(ui, 'VM Name', server.name)
81
+ msg_pair(ui, 'Size', server.size)
82
+ msg_pair(ui, 'Public Ip Address', server.publicipaddress)
83
+ puts "\n"
84
+
85
+ begin
86
+ ui.confirm('Do you really want to delete this server')
87
+ rescue SystemExit # Need to handle this as confirming with N/n raises SystemExit exception
88
+ server = nil # Cleanup is implicitly performed in other cloud plugins
89
+ exit!
90
+ end
91
+
92
+ params[:azure_dns_name] = server.hostedservicename
93
+
94
+ connection.roles.delete(params)
95
+
96
+ puts "\n"
97
+ ui.warn("Deleted server #{server.name}")
98
+ end
99
+
100
+ def show_server(name)
101
+ begin
102
+ role = connection.roles.find name
103
+
104
+ puts ''
105
+ if (role)
106
+ details = Array.new
107
+ details << ui.color('Role name', :bold, :cyan)
108
+ details << role.name
109
+ details << ui.color('Status', :bold, :cyan)
110
+ details << role.status
111
+ details << ui.color('Size', :bold, :cyan)
112
+ details << role.size
113
+ details << ui.color('Hosted service name', :bold, :cyan)
114
+ details << role.hostedservicename
115
+ details << ui.color('Deployment name', :bold, :cyan)
116
+ details << role.deployname
117
+ details << ui.color('Host name', :bold, :cyan)
118
+ details << role.hostname
119
+ unless role.sshport.nil?
120
+ details << ui.color('SSH port', :bold, :cyan)
121
+ details << role.sshport
122
+ end
123
+ unless role.winrmport.nil?
124
+ details << ui.color('WinRM port', :bold, :cyan)
125
+ details << role.winrmport
126
+ end
127
+ details << ui.color('Public IP', :bold, :cyan)
128
+ details << role.publicipaddress.to_s
129
+
130
+ unless role.thumbprint.empty?
131
+ details << ui.color('Thumbprint', :bold, :cyan)
132
+ details << role.thumbprint
133
+ end
134
+ puts ui.list(details, :columns_across, 2)
135
+ if role.tcpports.length > 0 || role.udpports.length > 0
136
+ details.clear
137
+ details << ui.color('Ports open', :bold, :cyan)
138
+ details << ui.color('Local port', :bold, :cyan)
139
+ details << ui.color('IP', :bold, :cyan)
140
+ details << ui.color('Public port', :bold, :cyan)
141
+ if role.tcpports.length > 0
142
+ role.tcpports.each do |port|
143
+ details << 'tcp'
144
+ details << port['LocalPort']
145
+ details << port['Vip']
146
+ details << port['PublicPort']
147
+ end
148
+ end
149
+ if role.udpports.length > 0
150
+ role.udpports.each do |port|
151
+ details << 'udp'
152
+ details << port['LocalPort']
153
+ details << port['Vip']
154
+ details << port['PublicPort']
155
+ end
156
+ end
157
+ puts ui.list(details, :columns_across, 4)
158
+ end
159
+ else
160
+ puts "No VM found"
161
+ end
162
+
163
+ rescue => error
164
+ puts "#{error.class} and #{error.message}"
165
+ end
166
+
167
+ end
168
+
169
+ def list_internal_lb
170
+ lbs = connection.lbs.all
171
+ cols = %w{Name Service Subnet VIP}
172
+ rows = []
173
+ lbs.each do |lb|
174
+ cols.each { |col| rows << lb.send(col.downcase).to_s }
175
+ end
176
+ display_list(ui, cols, rows)
177
+ end
178
+
179
+ def create_internal_lb(params = {})
180
+ connection.lbs.create(params)
181
+ end
182
+
183
+ def list_vnets
184
+ vnets = connection.vnets.all
185
+ cols = ['Name', 'Affinity Group', 'State']
186
+ rows = []
187
+ vnets.each do |vnet|
188
+ %w(name affinity_group state).each{ |col| rows << vnet.send(col).to_s }
189
+ end
190
+ display_list(ui, cols, rows)
191
+ end
192
+
193
+ def create_vnet(params = {})
194
+ connection.vnets.create(params)
195
+ end
196
+
197
+ def list_affinity_groups
198
+ affinity_groups = connection.ags.all
199
+ cols = %w{Name Location Description}
200
+ rows = []
201
+ affinity_groups.each do |affinity_group|
202
+ cols.each { |col| rows << affinity_group.send(col.downcase).to_s }
203
+ end
204
+ display_list(ui, cols, rows)
205
+ end
206
+
207
+ def create_affinity_group(params = {})
208
+ connection.ags.create(params)
209
+ end
210
+
211
+ def create_server(params = {})
212
+ remove_hosted_service_on_failure = params[:azure_dns_name]
213
+ if connection.hosts.exists?(params[:azure_dns_name])
214
+ remove_hosted_service_on_failure = nil
215
+ end
216
+
217
+ #If Storage Account is not specified, check if the geographic location has one to re-use
218
+ if not params[:azure_storage_account]
219
+ storage_accts = connection.storageaccounts.all
220
+ storage = storage_accts.find { |storage_acct| storage_acct.location.to_s == params[:azure_service_location] }
221
+ unless storage
222
+ params[:azure_storage_account] = [strip_non_ascii(params[:azure_vm_name]), random_string].join.downcase
223
+ remove_storage_service_on_failure = params[:azure_storage_account]
224
+ else
225
+ remove_storage_service_on_failure = nil
226
+ params[:azure_storage_account] = storage.name.to_s
227
+ end
228
+ else
229
+ if connection.storageaccounts.exists?(params[:azure_storage_account])
230
+ remove_storage_service_on_failure = nil
231
+ else
232
+ remove_storage_service_on_failure = params[:azure_storage_account]
233
+ end
234
+ end
235
+
236
+ begin
237
+ connection.deploys.create(params)
238
+ rescue Exception => e
239
+ Chef::Log.error("Failed to create the server -- exception being rescued: #{e.to_s}")
240
+ backtrace_message = "#{e.class}: #{e}\n#{e.backtrace.join("\n")}"
241
+ Chef::Log.debug("#{backtrace_message}")
242
+ cleanup_and_exit(remove_hosted_service_on_failure, remove_storage_service_on_failure)
243
+ end
244
+
245
+ end
246
+
247
+ def cleanup_and_exit(remove_hosted_service_on_failure, remove_storage_service_on_failure)
248
+ Chef::Log.warn("Cleaning up resources...")
249
+
250
+ if remove_hosted_service_on_failure
251
+ ret_val = connection.hosts.delete(remove_hosted_service_on_failure)
252
+ ret_val.content.empty? ? Chef::Log.warn("Deleted created DNS: #{remove_hosted_service_on_failure}.") : Chef::Log.warn("Deletion failed for created DNS:#{remove_hosted_service_on_failure}. " + ret_val.text)
253
+ end
254
+
255
+ if remove_storage_service_on_failure
256
+ ret_val = connection.storageaccounts.delete(remove_storage_service_on_failure)
257
+ ret_val.content.empty? ? Chef::Log.warn("Deleted created Storage Account: #{remove_storage_service_on_failure}.") : Chef::Log.warn("Deletion failed for created Storage Account: #{remove_storage_service_on_failure}. " + ret_val.text)
258
+ end
259
+ exit 1
260
+ end
261
+
262
+ def get_role_server(dns_name, vm_name)
263
+ deploy = connection.deploys.queryDeploy(dns_name)
264
+ deploy.find_role(vm_name)
265
+ end
266
+
267
+ def get_extension(name, publisher)
268
+ connection.query_azure("resourceextensions/#{publisher}/#{name}")
269
+ end
270
+
271
+ def deployment_name(dns_name)
272
+ connection.deploys.get_deploy_name_for_hostedservice(dns_name)
273
+ end
274
+
275
+ def deployment(path)
276
+ connection.query_azure(path)
277
+ end
278
+
279
+ def valid_image?(name)
280
+ connection.images.exists?(name)
281
+ end
282
+
283
+ def vm_image?(name)
284
+ connection.images.is_vm_image(name)
285
+ end
286
+
287
+ def add_extension(name, params = {})
288
+ begin
289
+ ui.info "Started with Chef Extension deployment on the server #{name}..."
290
+ connection.roles.update(name, params)
291
+ ui.info "\nSuccessfully deployed Chef Extension on the server #{name}."
292
+ rescue Exception => e
293
+ Chef::Log.error("Failed to add extension to the server -- exception being rescued: #{e.to_s}")
294
+ backtrace_message = "#{e.class}: #{e}\n#{e.backtrace.join("\n")}"
295
+ Chef::Log.debug("#{backtrace_message}")
296
+ end
297
+ end
298
+ end
299
+ end
300
+ end
301
+
@@ -16,7 +16,7 @@
16
16
  # limitations under the License.
17
17
  #
18
18
 
19
- class Azure
19
+ module Azure
20
20
  class AGs
21
21
  def initialize(connection)
22
22
  @connection = connection
@@ -58,7 +58,7 @@ class Azure
58
58
  end
59
59
  end
60
60
 
61
- class Azure
61
+ module Azure
62
62
  class AG
63
63
  attr_accessor :name, :label, :description, :location
64
64