knife-azure 1.8.7 → 1.9.0

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