knife-azure 1.6.0 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +201 -201
  3. data/README.md +37 -654
  4. data/lib/azure/resource_management/ARM_deployment_template.rb +87 -50
  5. data/lib/azure/resource_management/ARM_interface.rb +236 -516
  6. data/lib/azure/resource_management/vnet_config.rb +254 -0
  7. data/lib/azure/resource_management/windows_credentials.rb +109 -61
  8. data/lib/azure/service_management/ASM_interface.rb +17 -1
  9. data/lib/azure/service_management/certificate.rb +37 -13
  10. data/lib/azure/service_management/connection.rb +0 -0
  11. data/lib/azure/service_management/deploy.rb +0 -0
  12. data/lib/azure/service_management/disk.rb +0 -0
  13. data/lib/azure/service_management/host.rb +0 -0
  14. data/lib/azure/service_management/image.rb +0 -0
  15. data/lib/azure/service_management/rest.rb +0 -0
  16. data/lib/azure/service_management/role.rb +0 -0
  17. data/lib/azure/service_management/utility.rb +0 -0
  18. data/lib/chef/knife/azure_base.rb +100 -0
  19. data/lib/chef/knife/azure_image_list.rb +0 -0
  20. data/lib/chef/knife/azure_server_create.rb +0 -98
  21. data/lib/chef/knife/azure_server_delete.rb +0 -0
  22. data/lib/chef/knife/azure_server_list.rb +0 -0
  23. data/lib/chef/knife/azure_server_show.rb +0 -0
  24. data/lib/chef/knife/azurerm_base.rb +42 -9
  25. data/lib/chef/knife/azurerm_server_create.rb +31 -24
  26. data/lib/chef/knife/azurerm_server_delete.rb +1 -10
  27. data/lib/chef/knife/azurerm_server_list.rb +5 -1
  28. data/lib/chef/knife/azurerm_server_show.rb +5 -1
  29. data/lib/chef/knife/bootstrap/bootstrap_options.rb +12 -18
  30. data/lib/chef/knife/bootstrap/bootstrapper.rb +34 -15
  31. data/lib/chef/knife/bootstrap/common_bootstrap_options.rb +21 -24
  32. data/lib/chef/knife/bootstrap_azure.rb +58 -0
  33. data/lib/chef/knife/bootstrap_azurerm.rb +40 -50
  34. data/lib/knife-azure/version.rb +1 -1
  35. metadata +27 -12
@@ -0,0 +1,254 @@
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 'ipaddress'
21
+
22
+ module Azure::ARM
23
+ module VnetConfig
24
+
25
+ ## lists subnets of only a specific virtual network address space ##
26
+ def subnets_list_for_specific_address_space(address_prefix, subnets_list)
27
+ list = []
28
+ address_space = IPAddress(address_prefix)
29
+ subnets_list.each do |sbn|
30
+ subnet_address_prefix = IPAddress(sbn.properties.address_prefix)
31
+
32
+ ## check if the subnet belongs to this address space or not ##
33
+ list << sbn if address_space.include? subnet_address_prefix
34
+ end
35
+
36
+ list
37
+ end
38
+
39
+ def get_vnet(resource_group_name, vnet_name)
40
+ begin
41
+ network_resource_client.virtual_networks.get(resource_group_name, vnet_name)
42
+ rescue MsRestAzure::AzureOperationError => error
43
+ if error.body
44
+ err_json = JSON.parse(error.response.body)
45
+ if err_json['error']['code'] == "ResourceNotFound"
46
+ return false
47
+ else
48
+ raise error
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ ## lists all subnets under a virtual network or lists subnets of only a particular address space ##
55
+ def subnets_list(resource_group_name, vnet_name, address_prefix = nil)
56
+ list = network_resource_client.subnets.list(resource_group_name, vnet_name).value
57
+ !address_prefix.nil? && !list.empty? ? subnets_list_for_specific_address_space(address_prefix, list) : list
58
+ end
59
+
60
+ ## single subnet body creation to be added in template ##
61
+ def subnet(subnet_name, subnet_prefix)
62
+ {
63
+ 'name'=> subnet_name,
64
+ 'properties'=> {
65
+ 'addressPrefix'=> subnet_prefix
66
+ }
67
+ }
68
+ end
69
+
70
+ ## return all the address prefixes under a virtual network ##
71
+ def vnet_address_spaces(vnet)
72
+ vnet.properties.address_space.address_prefixes
73
+ end
74
+
75
+ ## return address prefix of a subnet ##
76
+ def subnet_address_prefix(subnet)
77
+ subnet.properties.address_prefix
78
+ end
79
+
80
+ ## sort available networks pool in ascending order based on the network's
81
+ ## IP address to allocate the network for the new subnet to be added in the
82
+ ## existing virtual network ##
83
+ def sort_available_networks(available_networks)
84
+ available_networks.sort_by { |nwrk| nwrk.network.address.split('.').map(&:to_i) }
85
+ end
86
+
87
+ ## sort existing subnets in ascending order based on their cidr prefix or
88
+ ## netmask to have subnets with larger networks on the top ##
89
+ def sort_subnets_by_cidr_prefix(subnets)
90
+ subnets.sort_by { |sbn| [ subnet_address_prefix(sbn).split('/')[1] ].map(&:to_i) }
91
+ end
92
+
93
+ ## sort used networks pool in descending order based on the number of hosts
94
+ ## it contains, this helps to keep larger networks on top thereby eliminating
95
+ ## more number of entries in available_networks_pool at a faster pace ##
96
+ def sort_used_networks_by_hosts_size(used_network)
97
+ used_network.sort_by { |nwrk| -nwrk.hosts.size }
98
+ end
99
+
100
+ ## return the cidr prefix or netmask of the given subnet ##
101
+ def subnet_cidr_prefix(subnet)
102
+ subnet_address_prefix(subnet).split('/')[1].to_i
103
+ end
104
+
105
+ ## method to invoke respective sort methods for the network pools ##
106
+ def sort_pools(available_networks_pool, used_networks_pool)
107
+ return sort_available_networks(available_networks_pool), sort_used_networks_by_hosts_size(used_networks_pool)
108
+ end
109
+
110
+ ## when a address space in an existing virtual network is not used at all
111
+ ## then divide the space into the number of subnets based on the total
112
+ ## number of hosts that network supports ##
113
+ def divide_network(address_prefix)
114
+ network_address = IPAddress(address_prefix)
115
+ prefix = nil
116
+
117
+ case network_address.count
118
+ when 4097..65536
119
+ prefix = '20'
120
+ when 256..4096
121
+ prefix = '24'
122
+ end
123
+
124
+ ## if the given network is small then do not divide it else divide using
125
+ ## the prefix value calculated above ##
126
+ prefix.nil? ? address_prefix : network_address.network.address.concat('/' + prefix)
127
+ end
128
+
129
+ ## check if the available_network is part of the subnet_network or vice-versa ##
130
+ def in_use_network?(subnet_network, available_network)
131
+ (subnet_network.include? available_network) ||
132
+ (available_network.include? subnet_network)
133
+ end
134
+
135
+ ## calculate and return address_prefix for the new subnet to be added in the
136
+ ## existing virtual network ##
137
+ def new_subnet_address_prefix(vnet_address_prefix, subnets)
138
+ if subnets.empty? ## no subnets exist in the given address space of the virtual network, so divide the network into smaller subnets (based on the network size) and allocate space for the new subnet to be added ##
139
+ divide_network(vnet_address_prefix)
140
+ else ## subnets exist in vnet, calculate new address_prefix for the new subnet based on the space taken by these existing subnets under the given address space of the virtual network ##
141
+ vnet_network_address = IPAddress(vnet_address_prefix)
142
+ subnets = sort_subnets_by_cidr_prefix(subnets)
143
+ available_networks_pool = Array.new
144
+ used_networks_pool = Array.new
145
+ subnets.each do |subnet|
146
+ ## in case the larger network is not divided into smaller subnets but
147
+ ## divided into only 1 largest subnet of the complete network size ##
148
+ if vnet_network_address.prefix == subnet_cidr_prefix(subnet)
149
+ break
150
+ end
151
+
152
+ ## add all the possible subnets (calculated using the current subnet's
153
+ ## cidr prefix value) into the available_networks_pool ##
154
+ available_networks_pool.push(
155
+ vnet_network_address.subnet(subnet_cidr_prefix(subnet))
156
+ ).flatten!.uniq! { |nwrk| [ nwrk.network.address, nwrk.prefix ].join(':') }
157
+
158
+ ## add current subnet into the used_networks_pool ##
159
+ used_networks_pool.push(
160
+ IPAddress(subnet_address_prefix(subnet))
161
+ )
162
+
163
+ ## sort both the network pools before trimming the available_networks_pool ##
164
+ available_networks_pool, used_networks_pool = sort_pools(
165
+ available_networks_pool, used_networks_pool)
166
+
167
+ ## trim the available_networks_pool based on the networks already
168
+ ## allocated to the existing subnets ##
169
+ used_networks_pool.each do |subnet_network|
170
+ available_networks_pool.delete_if {
171
+ |available_network| in_use_network?(subnet_network, available_network)
172
+ }
173
+ end
174
+
175
+ ## sort both the network pools after trimming the available_networks_pool ##
176
+ available_networks_pool, used_networks_pool = sort_pools(
177
+ available_networks_pool, used_networks_pool)
178
+ end
179
+
180
+ ## space available in the vnet_address_prefix network for the new subnet ##
181
+ if !available_networks_pool.empty? && available_networks_pool.first.network?
182
+ available_networks_pool.first.network.address.concat("/" + available_networks_pool.first.prefix.to_s)
183
+ else ## space not available in the vnet_address_prefix network for the new subnet ##
184
+ nil
185
+ end
186
+ end
187
+ end
188
+
189
+ ## add new subnet into the existing virtual network ##
190
+ def add_subnet(subnet_name, vnet_config, subnets)
191
+ new_subnet_prefix = nil
192
+ vnet_address_prefix_count = 0
193
+ vnet_address_space = vnet_config[:addressPrefixes]
194
+
195
+ ## search for space in all the address prefixes of the virtual network ##
196
+ while new_subnet_prefix.nil? && vnet_address_space.length > vnet_address_prefix_count
197
+ new_subnet_prefix = new_subnet_address_prefix(
198
+ vnet_address_space[vnet_address_prefix_count],
199
+ subnets_list_for_specific_address_space(
200
+ vnet_address_space[vnet_address_prefix_count], subnets
201
+ )
202
+ )
203
+ vnet_address_prefix_count = vnet_address_prefix_count + 1
204
+ end
205
+
206
+ if new_subnet_prefix ## found space for new subnet ##
207
+ vnet_config[:subnets].push(
208
+ subnet(subnet_name, new_subnet_prefix)
209
+ )
210
+ else ## no space available in the virtual network for the new subnet ##
211
+ raise "Unable to add subnet #{subnet_name} into the virtual network #{vnet_config[:virtualNetworkName]}, no address space available !!!"
212
+ end
213
+
214
+ vnet_config
215
+ end
216
+
217
+ ## virtual network configuration creation for the new vnet creation or to
218
+ ## handle existing vnet ##
219
+ def create_vnet_config(resource_group_name, vnet_name, vnet_subnet_name)
220
+ raise ArgumentError, 'GatewaySubnet cannot be used as the name for --azure-vnet-subnet-name option. GatewaySubnet can only be used for virtual network gateways.' if vnet_subnet_name == 'GatewaySubnet'
221
+
222
+ vnet_config = {}
223
+ subnets = nil
224
+ flag = true
225
+ ## check whether user passed or default named virtual network exist or not ##
226
+ vnet = get_vnet(resource_group_name, vnet_name)
227
+ vnet_config[:virtualNetworkName] = vnet_name
228
+ if vnet ## handle resources in the existing virtual network ##
229
+ vnet_config[:addressPrefixes] = vnet_address_spaces(vnet)
230
+ vnet_config[:subnets] = Array.new
231
+ subnets = subnets_list(resource_group_name, vnet_name)
232
+ subnets.each do |subnet|
233
+ flag = false if subnet.name == vnet_subnet_name ## given subnet already exist in the virtual network ##
234
+ ## preserve the existing subnet resources ##
235
+ vnet_config[:subnets].push(
236
+ subnet(subnet.name, subnet_address_prefix(subnet))
237
+ )
238
+ end if subnets
239
+ else ## create config for new vnet ##
240
+ vnet_config[:addressPrefixes] = [ "10.0.0.0/16" ]
241
+ vnet_config[:subnets] = Array.new
242
+ vnet_config[:subnets].push(
243
+ subnet(vnet_subnet_name, "10.0.0.0/24")
244
+ )
245
+ flag = false
246
+ end
247
+
248
+ ## given subnet does not exist, so create new one in the virtual network ##
249
+ vnet_config = add_subnet(vnet_subnet_name, vnet_config, subnets) if flag
250
+
251
+ vnet_config
252
+ end
253
+ end
254
+ end
@@ -21,11 +21,13 @@
21
21
  require 'chef'
22
22
  require 'mixlib/shellout'
23
23
  require 'ffi'
24
+ require "chef/win32/api"
24
25
 
25
26
  module Azure::ARM
26
27
 
27
28
  module ReadCred
28
29
 
30
+ extend Chef::ReservedNames::Win32::API
29
31
  extend FFI::Library
30
32
 
31
33
  ffi_lib 'Advapi32'
@@ -37,36 +39,36 @@ module Azure::ARM
37
39
 
38
40
  # Ref: https://msdn.microsoft.com/en-us/library/windows/desktop/ms724284(v=vs.85).aspx
39
41
  class FILETIME < FFI::Struct
40
- layout :dwLowDateTime, :uint32,
41
- :dwHighDateTime, :uint32
42
+ layout :dwLowDateTime, :DWORD,
43
+ :dwHighDateTime, :DWORD
42
44
  end
43
45
 
44
46
  # Ref: https://msdn.microsoft.com/en-us/library/windows/desktop/aa374790(v=vs.85).aspx
45
47
  class CREDENTIAL_ATTRIBUTE < FFI::Struct
46
- layout :Keyword, :pointer,
47
- :Flags, :uint32,
48
- :ValueSize, :uint32,
49
- :Value, :pointer
48
+ layout :Keyword, :LPTSTR,
49
+ :Flags, :DWORD,
50
+ :ValueSize, :DWORD,
51
+ :Value, :LPBYTE
50
52
  end
51
53
 
52
54
  # Ref: https://msdn.microsoft.com/en-us/library/windows/desktop/aa374788(v=vs.85).aspx
53
55
  class CREDENTIAL_OBJECT < FFI::Struct
54
- layout :Flags, :uint32,
55
- :Type, :uint32,
56
- :TargetName, :pointer,
57
- :Comment, :pointer,
56
+ layout :Flags, :DWORD,
57
+ :Type, :DWORD,
58
+ :TargetName, :LPTSTR,
59
+ :Comment, :LPTSTR,
58
60
  :LastWritten, FILETIME,
59
- :CredentialBlobSize, :uint32,
60
- :CredentialBlob, :pointer,
61
- :Persist, :uint32,
62
- :AttributeCount, :uint32,
61
+ :CredentialBlobSize, :DWORD,
62
+ :CredentialBlob, :LPBYTE,
63
+ :Persist, :DWORD,
64
+ :AttributeCount, :DWORD,
63
65
  :Attributes, CREDENTIAL_ATTRIBUTE,
64
- :TargetAlias, :pointer,
65
- :UserName, :pointer
66
+ :TargetAlias, :LPTSTR,
67
+ :UserName, :LPTSTR
66
68
  end
67
69
 
68
70
  # Ref: https://msdn.microsoft.com/en-us/library/windows/desktop/aa374804(v=vs.85).aspx
69
- attach_function :CredReadW, [:pointer, :uint32, :uint32, :pointer], :bool
71
+ safe_attach_function :CredReadW, [:LPCTSTR, :DWORD, :DWORD, :pointer], :BOOL
70
72
  end
71
73
 
72
74
 
@@ -76,61 +78,107 @@ module Azure::ARM
76
78
  include ReadCred
77
79
 
78
80
  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"
81
+ begin
82
+ target = target_name
83
+
84
+ if target && !target.empty?
85
+ target_pointer = wstring(target)
86
+ info_ptr = FFI::MemoryPointer.new(:pointer)
87
+ cred = CREDENTIAL_OBJECT.new info_ptr
88
+ cred_result = CredReadW(target_pointer, CRED_TYPE_GENERIC, 0, cred)
89
+ translated_cred = CREDENTIAL_OBJECT.new(info_ptr.read_pointer)
90
+
91
+ target_obj = translated_cred[:TargetName].read_wstring.split("::") if translated_cred[:TargetName].read_wstring
92
+ cred_blob = translated_cred[:CredentialBlob].get_bytes(0, translated_cred[:CredentialBlobSize]).split("::")
93
+
94
+ tokentype = target_obj.select { |obj| obj.include? "tokenType" }
95
+ user = target_obj.select { |obj| obj.include? "userId" }
96
+ clientid = target_obj.select { |obj| obj.include? "clientId" }
97
+ expiry_time = target_obj.select { |obj| obj.include? "expiresOn" }
98
+ access_token = cred_blob.select { |obj| obj.include? "a:" }
99
+ refresh_token = cred_blob.select { |obj| obj.include? "r:" }
100
+
101
+ credential = {}
102
+ credential[:tokentype] = tokentype[0].split(":")[1]
103
+ credential[:user] = user[0].split(":")[1]
104
+ credential[:token] = access_token[0].split(":")[1]
105
+ #Todo: refresh_token is not complete currently
106
+ # target_name method needs to be modified for that
107
+ credential[:refresh_token] = refresh_token[0].split(":")[1]
108
+ credential[:clientid] = clientid[0].split(":")[1]
109
+ credential[:expiry_time] = expiry_time[0].split("expiresOn:")[1].gsub("\\","")
110
+
111
+ # Free memory pointed by info_ptr
112
+ info_ptr.free
113
+ else
114
+ raise "TargetName Not Found"
115
+ end
116
+ credential
117
+ rescue => error
118
+ ui.error("#{error.message}")
119
+ Chef::Log.debug("#{error.backtrace.join("\n")}")
120
+ exit
107
121
  end
108
- credential
109
122
  end
110
123
 
124
+ #Todo: For getting the complete refreshToken, both credentials (ending with --0-2 and --1-2) have to be read
111
125
  def target_name
112
- # cmdkey command is used for accessing windows credential manager
113
- xplat_creds_cmd = Mixlib::ShellOut.new("cmdkey /list | findstr AzureXplatCli")
126
+ # cmdkey command is used for accessing windows credential manager.
127
+ # Multiple credentials get created in windows credential manager for a single Azure account in xplat-cli
128
+ # One of them is for common tanent id, which can't be used
129
+ # Others end with --0-x,--1-x,--2-x etc, where x represents the total no. of credentails across which the token is divided
130
+ # The one ending with --0-x has the complete accessToken in the credentialBlob.
131
+ # Refresh Token is split across both credentials (ending with --0-x and --1-x).
132
+ # Xplat splits the credentials based on the number of bytes of the tokens.
133
+ # Hence the access token is always found in the one which start with --0-
134
+ # So selecting the credential on the basis of --0-
135
+ xplat_creds_cmd = Mixlib::ShellOut.new('cmdkey /list | findstr AzureXplatCli | findstr \--0- | findstr -v common')
114
136
  result = xplat_creds_cmd.run_command
137
+ target_names = []
115
138
 
116
- target_name = ""
117
139
  if result.stdout.empty?
118
- raise "Azure Credentials not found. Please run xplat's 'azure login' command"
140
+ Chef::Log.debug("Unable to find a credential with --0- and falling back to looking for any credential.")
141
+ xplat_creds_cmd = Mixlib::ShellOut.new('cmdkey /list | findstr AzureXplatCli | findstr -v common')
142
+ result = xplat_creds_cmd.run_command
143
+
144
+ if result.stdout.empty?
145
+ raise "Azure Credentials not found. Please run xplat's 'azure login' command"
146
+ else
147
+ target_names = result.stdout.split("\n")
148
+ end
149
+ else
150
+ target_names = result.stdout.split("\n")
151
+ end
152
+
153
+ # If "azure login" is run for multiple users, there will be multiple credentials
154
+ # Picking up the latest logged in user's credentials
155
+ latest_target = latest_credential_target target_names
156
+ latest_target
157
+ end
158
+
159
+ def latest_credential_target targets
160
+ case targets.size
161
+ when 0
162
+ raise "No Target was found for windows credentials"
163
+ when 1
164
+ return targets.first.gsub("Target:","").strip
119
165
  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
166
+ latest_target = ""
167
+ max_expiry_time = Time.new(0)
168
+
169
+ # Using expiry_time to determine the latest credential
170
+ targets.each do |target|
171
+ target_obj = target.split("::")
172
+ expiry_time_obj = target_obj.select { |obj| obj.include? "expiresOn" }
173
+ expiry_time = expiry_time_obj[0].split("expiresOn:")[1].gsub("\\","")
174
+ if Time.parse(expiry_time) > max_expiry_time
175
+ latest_target = target
176
+ max_expiry_time = Time.parse(expiry_time)
129
177
  end
130
178
  end
131
- end
132
179
 
133
- target_name
180
+ return latest_target.gsub("Target:","").strip
181
+ end
134
182
  end
135
183
  end
136
184
  end