kitchen-azurerm 1.13.6 → 1.14.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d35fbe96cb5105531f593d3c43343de784274f46e1efe095c399074e8cb8f8f2
4
- data.tar.gz: 7c5deaf7be58a437b19244d6dfa70358c1d1e6ea218f9d8b0efda28e92ca0a8f
3
+ metadata.gz: 2c8f93443204a7cb375624116dbfafaa5ade3419fdd9e0538e1c701f38e60d29
4
+ data.tar.gz: e14d346d4cb82139aaaa49fdb9d4ef7f752059e0b535d24285e37a585c5b710b
5
5
  SHA512:
6
- metadata.gz: 94b15b90e66ee2d5b83ee65ca5087a5523fca00bc938d95a7d6979e3b69eb337b8b0f94aad553d348dd14df0941cc83082190dbf772fe29e923940f945893af8
7
- data.tar.gz: bf15ffcb819adf6b1d71eb520391d0b39a8eb899d2df01ee4e304032fdef96858b389001de7f0d0559e71f0018eb42fddd8a2b0d766df16d7965da6220c9a41d
6
+ metadata.gz: 2474a4e576e924afaa92e121fc315fee4cbce2ab159f946d64d8e208f1127d8dd50fd7d383aafc70beec2bb3dfb9194ddaf79d43c5e4cd1522787a4d38603efa
7
+ data.tar.gz: 9e498c0a069a4ee74e7db388c5983df082ba1bf251be06280868abdc802de616f75a93e331b62527a39b839cf55927fcab741265d732eef64f460e34130099e5
@@ -1,42 +1,96 @@
1
1
  require "inifile"
2
2
 
3
+ require "kitchen/errors"
3
4
  require "kitchen/logging"
4
5
  autoload :MsRest2, "ms_rest2"
5
6
  autoload :MsRestAzure2, "ms_rest_azure2"
6
7
 
7
8
  module Kitchen
8
9
  module Driver
10
+ # Resolves Azure Resource Manager credentials and endpoint settings for a
11
+ # single subscription.
9
12
  #
10
- # AzureCredentials
13
+ # Credentials are sourced, in order of precedence, from environment
14
+ # variables (+AZURE_TENANT_ID+, +AZURE_CLIENT_ID+, +AZURE_CLIENT_SECRET+)
15
+ # and then from the Azure CLI credentials INI file (by default
16
+ # +~/.azure/credentials+, overridable with +AZURE_CONFIG_FILE+).
11
17
  #
18
+ # The combination of values that resolve determines which token provider is
19
+ # used - see {#azure_options}.
20
+ #
21
+ # @example Service principal supplied by environment
22
+ # ENV["AZURE_TENANT_ID"] = "..."
23
+ # ENV["AZURE_CLIENT_ID"] = "..."
24
+ # ENV["AZURE_CLIENT_SECRET"] = "..."
25
+ # options = Kitchen::Driver::AzureCredentials.new(subscription_id: "...").azure_options
12
26
  class AzureCredentials
13
27
  include Kitchen::Logging
14
28
 
15
- CONFIG_PATH = "#{ENV["HOME"]}/.azure/credentials".freeze
16
-
29
+ # Path fragment, relative to the user's home directory, of the Azure CLI
30
+ # credentials file.
17
31
  #
18
32
  # @return [String]
33
+ CONFIG_FILE = File.join(".azure", "credentials").freeze
34
+
35
+ # Azure cloud names understood by {#azure_options}, mapped to the
36
+ # +MsRestAzure2+ constants that describe them. Keys are downcased so
37
+ # lookups are case-insensitive.
19
38
  #
20
- attr_reader :subscription_id
39
+ # @return [Hash{String => Symbol}]
40
+ ENVIRONMENTS = {
41
+ "azure" => :Azure,
42
+ "azurechina" => :AzureChina,
43
+ "azuregermancloud" => :AzureGermanCloud,
44
+ "azureusgovernment" => :AzureUSGovernment,
45
+ }.freeze
21
46
 
47
+ # Port the Azure Instance Metadata Service listens on for MSI token requests.
48
+ #
49
+ # @return [Integer]
50
+ MSI_PORT = 50342
51
+
52
+ # The Azure subscription these credentials authenticate against.
22
53
  #
23
54
  # @return [String]
55
+ attr_reader :subscription_id
56
+
57
+ # The Azure cloud name, e.g. +"Azure"+ or +"AzureUSGovernment"+.
24
58
  #
59
+ # @return [String]
25
60
  attr_reader :environment
26
61
 
62
+ # Default path of the Azure CLI credentials file.
27
63
  #
28
- # Creates and initializes a new instance of the Credentials class.
64
+ # Resolved lazily (rather than at load time) so that a test - or a caller
65
+ # that manipulates +HOME+ - sees the current home directory rather than
66
+ # whichever one happened to be set when this file was first required.
29
67
  #
68
+ # @return [String] absolute path to +~/.azure/credentials+
69
+ def self.default_config_path
70
+ File.join(Dir.home, CONFIG_FILE)
71
+ end
72
+
73
+ # @param subscription_id [String] the Azure subscription to authenticate against.
74
+ # @param environment [String] the Azure cloud name. Case-insensitive.
75
+ # @raise [Kitchen::UserError] if +environment+ is not a known Azure cloud.
30
76
  def initialize(subscription_id:, environment: "Azure")
31
77
  @subscription_id = subscription_id
32
- @environment = environment
78
+ @environment = environment || "Azure"
79
+
80
+ unless ENVIRONMENTS.key?(@environment.to_s.downcase)
81
+ raise Kitchen::UserError,
82
+ "Unknown azure_environment '#{@environment}'. Valid values are: #{ENVIRONMENTS.keys.join(", ")} (case-insensitive)."
83
+ end
33
84
  end
34
85
 
86
+ # Builds the options hash accepted by every +azure_mgmt_*2+ client.
35
87
  #
36
- # Retrieves an object containing options and credentials
37
- #
38
- # @return [Object] Object that can be supplied along with all Azure client requests.
88
+ # The +:credentials+ entry wraps whichever token provider matches the
89
+ # resolved credentials - see {#token_provider}. +:client_id+ and
90
+ # +:client_secret+ are only included when they resolve to a value.
39
91
  #
92
+ # @return [Hash] options suitable for
93
+ # +Azure::Resources2::Profiles::Latest::Mgmt::Client.new+ and friends.
40
94
  def azure_options
41
95
  options = { tenant_id: tenant_id!,
42
96
  subscription_id:,
@@ -48,16 +102,76 @@ module Kitchen
48
102
  options
49
103
  end
50
104
 
51
- private
105
+ # Selects a token provider based on which credentials resolved.
106
+ #
107
+ # * +client_id+ + +client_secret+ + +tenant_id+ - service principal.
108
+ # * +client_id+ + +tenant_id+ - user-assigned managed identity.
109
+ # * +tenant_id+ only - system-assigned managed identity.
110
+ # * none of the above - falls back to the +az login+ token cache.
111
+ #
112
+ # @return [MsRestAzure2::ApplicationTokenProvider,
113
+ # MsRestAzure2::MSITokenProvider, MsRestAzure2::AzureCliTokenProvider]
114
+ def token_provider
115
+ if client_id && client_secret && tenant_id
116
+ ::MsRestAzure2::ApplicationTokenProvider.new(tenant_id, client_id, client_secret, ad_settings)
117
+ elsif client_id && tenant_id
118
+ ::MsRestAzure2::MSITokenProvider.new(MSI_PORT, ad_settings, { client_id: })
119
+ elsif tenant_id
120
+ ::MsRestAzure2::MSITokenProvider.new(MSI_PORT, ad_settings)
121
+ else
122
+ warn("Using tenant id set through `az login`.")
123
+ ::MsRestAzure2::AzureCliTokenProvider.new(ad_settings)
124
+ end
125
+ end
52
126
 
53
- def logger
54
- Kitchen.logger
127
+ # Active Directory settings for the configured cloud.
128
+ #
129
+ # @return [MsRestAzure2::ActiveDirectoryServiceSettings]
130
+ def ad_settings
131
+ case environment_key
132
+ when :AzureUSGovernment then ::MsRestAzure2::ActiveDirectoryServiceSettings.get_azure_us_government_settings
133
+ when :AzureChina then ::MsRestAzure2::ActiveDirectoryServiceSettings.get_azure_china_settings
134
+ when :AzureGermanCloud then ::MsRestAzure2::ActiveDirectoryServiceSettings.get_azure_german_settings
135
+ else ::MsRestAzure2::ActiveDirectoryServiceSettings.get_azure_settings
136
+ end
137
+ end
138
+
139
+ # Endpoint settings (resource manager URL, storage suffixes, ...) for the
140
+ # configured cloud.
141
+ #
142
+ # @return [MsRestAzure2::AzureEnvironment]
143
+ def endpoint_settings
144
+ case environment_key
145
+ when :AzureUSGovernment then ::MsRestAzure2::AzureEnvironments::AzureUSGovernment
146
+ when :AzureChina then ::MsRestAzure2::AzureEnvironments::AzureChinaCloud
147
+ when :AzureGermanCloud then ::MsRestAzure2::AzureEnvironments::AzureGermanCloud
148
+ else ::MsRestAzure2::AzureEnvironments::AzureCloud
149
+ end
55
150
  end
56
151
 
152
+ # Path of the credentials file actually in use.
153
+ #
154
+ # @return [String] +AZURE_CONFIG_FILE+ if set, otherwise
155
+ # {.default_config_path}. Always expanded.
57
156
  def config_path
58
- @config_path ||= File.expand_path(ENV["AZURE_CONFIG_FILE"] || CONFIG_PATH)
157
+ @config_path ||= File.expand_path(ENV["AZURE_CONFIG_FILE"] || self.class.default_config_path)
158
+ end
159
+
160
+ private
161
+
162
+ # @return [Symbol] the {ENVIRONMENTS} key for the configured cloud.
163
+ def environment_key
164
+ ENVIRONMENTS.fetch(environment.to_s.downcase)
165
+ end
166
+
167
+ # @return [Kitchen::Logger] the shared Test Kitchen logger.
168
+ def logger
169
+ Kitchen.logger
59
170
  end
60
171
 
172
+ # Parsed credentials file, or an empty Hash when no readable file exists.
173
+ #
174
+ # @return [IniFile, Hash]
61
175
  def credentials
62
176
  @credentials ||= if File.file?(config_path)
63
177
  IniFile.load(config_path)
@@ -67,94 +181,51 @@ module Kitchen
67
181
  end
68
182
  end
69
183
 
184
+ # Reads a property from the section of the credentials file matching
185
+ # {#subscription_id}.
186
+ #
187
+ # @param property [String] the INI key to read.
188
+ # @return [String, nil]
70
189
  def credentials_property(property)
71
- credentials[subscription_id]&.[](property)
190
+ value = credentials[subscription_id]&.[](property)
191
+ value unless value.to_s.empty?
72
192
  end
73
193
 
194
+ # Tenant ID, warning the user when one cannot be resolved.
195
+ #
196
+ # @return [String, nil]
74
197
  def tenant_id!
75
198
  tenant_id || warn("(#{config_path}) does not contain tenant_id neither is the AZURE_TENANT_ID environment variable set.")
76
199
  end
77
200
 
201
+ # @return [String, nil] tenant ID from the environment or credentials file.
78
202
  def tenant_id
79
- ENV["AZURE_TENANT_ID"] || credentials_property("tenant_id")
203
+ env_or_credentials("AZURE_TENANT_ID", "tenant_id")
80
204
  end
81
205
 
206
+ # @return [String, nil] client ID from the environment or credentials file.
82
207
  def client_id
83
- ENV["AZURE_CLIENT_ID"] || credentials_property("client_id")
208
+ env_or_credentials("AZURE_CLIENT_ID", "client_id")
84
209
  end
85
210
 
211
+ # @return [String, nil] client secret from the environment or credentials file.
86
212
  def client_secret
87
- ENV["AZURE_CLIENT_SECRET"] || credentials_property("client_secret")
213
+ env_or_credentials("AZURE_CLIENT_SECRET", "client_secret")
88
214
  end
89
215
 
90
- # Retrieve a token based upon the preferred authentication method.
216
+ # Reads a value from the environment, falling back to the credentials file.
91
217
  #
92
- # @return [::MsRest2::TokenProvider] A new token provider object.
93
- def token_provider
94
- # Login with a credentials file or setting the environment variables
95
- #
96
- # Typically used with a service principal.
97
- #
98
- # SPN with client_id, client_secret and tenant_id
99
- if client_id && client_secret && tenant_id
100
- ::MsRestAzure2::ApplicationTokenProvider.new(tenant_id, client_id, client_secret, ad_settings)
101
- # Login with a Managed Service Identity.
102
- #
103
- # Typically used with a Managed Service Identity when you have a particular object registered in a tenant.
104
- #
105
- # MSI with client_id and tenant_id (aka User Assigned Identity).
106
- elsif client_id && tenant_id
107
- ::MsRestAzure2::MSITokenProvider.new(50342, ad_settings, { client_id: })
108
- # Default approach to inheriting existing object permissions (application or device this code is running on).
109
- #
110
- # Typically used when you want to inherit the permissions of the system you're running on that are in a tenant.
111
- #
112
- # MSI with just tenant_id (aka System Assigned Identity).
113
- elsif tenant_id
114
- ::MsRestAzure2::MSITokenProvider.new(50342, ad_settings)
115
- # Login using the Azure CLI
116
- #
117
- # Typically used when you want to rely upon `az login` as your preferred authentication method.
118
- else
119
- warn("Using tenant id set through `az login`.")
120
- ::MsRestAzure2::AzureCliTokenProvider.new(ad_settings)
121
- end
122
- end
123
-
124
- #
125
- # Retrieves a [MsRestAzure2::ActiveDirectoryServiceSettings] object representing the AD settings for the given cloud.
126
- #
127
- # @return [MsRestAzure2::ActiveDirectoryServiceSettings] Settings to be used for subsequent requests
218
+ # Empty environment variables are treated as unset - an exported-but-blank
219
+ # +AZURE_CLIENT_SECRET+ should not shadow a real value in the file.
128
220
  #
129
- def ad_settings
130
- case environment.downcase
131
- when "azureusgovernment"
132
- ::MsRestAzure2::ActiveDirectoryServiceSettings.get_azure_us_government_settings
133
- when "azurechina"
134
- ::MsRestAzure2::ActiveDirectoryServiceSettings.get_azure_china_settings
135
- when "azuregermancloud"
136
- ::MsRestAzure2::ActiveDirectoryServiceSettings.get_azure_german_settings
137
- when "azure"
138
- ::MsRestAzure2::ActiveDirectoryServiceSettings.get_azure_settings
139
- end
140
- end
221
+ # @param env_var [String] environment variable name.
222
+ # @param property [String] INI property name.
223
+ # @return [String, nil]
224
+ def env_or_credentials(env_var, property)
225
+ value = ENV[env_var]
226
+ return value unless value.to_s.empty?
141
227
 
142
- #
143
- # Retrieves a [MsRestAzure2::AzureEnvironment] object representing endpoint settings for the given cloud.
144
- #
145
- # @return [MsRestAzure2::AzureEnvironment] Settings to be used for subsequent requests
146
- #
147
- def endpoint_settings
148
- case environment.downcase
149
- when "azureusgovernment"
150
- ::MsRestAzure2::AzureEnvironments::AzureUSGovernment
151
- when "azurechina"
152
- ::MsRestAzure2::AzureEnvironments::AzureChinaCloud
153
- when "azuregermancloud"
154
- ::MsRestAzure2::AzureEnvironments::AzureGermanCloud
155
- when "azure"
156
- ::MsRestAzure2::AzureEnvironments::AzureCloud
157
- end
228
+ credentials_property(property)
158
229
  end
159
230
  end
160
231
  end